[Tip] jest Tips
keywords: test
, test-driven development
, TDD
, tips
更多 mock function 的範例
mock custom hooks
方式一
import { render, screen } from '@testing-library/react';
import App from './App';
/**
* STEP 1: import and mock custom hooks
**/
import useTodos from './components/TodoApp/useTodos';
jest.mock('./components/TodoApp/useTodos');
test('renders learn react link', async () => {
// STEP 2: implement the mock return value
useTodos.mockImplementation(() => ({
todos: [
{
userId: 1,
id: 1,
title: 'foobar',
completed: true,
},
],
}));
render(<App />);
const header = await screen.findByRole('heading', { name: 'Tasks' });
const todos = await screen.findByTestId('todos');
expect(header).toBeInTheDocument();
expect(todos.children.length).toBe(200);
});
方式二
import { render, screen } from '@testing-library/react';
import App from './App';
/**
* STEP 1: import and mock custom hooks
**/
import useTodos from './components/TodoApp/useTodos';
/**
* STEP 2: mock customized hook with return value
**/
jest.mock('./components/TodoApp/useTodos', {
_esModule: true,
useTodos: () => ({
todos: [
{
userId: 1,
id: 1,
title: 'foobar',
completed: true,
},
],
}),
});
test('renders learn react link', async () => {
render(<App />);
const header = await screen.findByRole('heading', { name: 'Tasks' });
const todos = await screen.findByTestId('todos');
expect(header).toBeInTheDocument();
expect(todos.children.length).toBe(200);
});
mock SWR
方式一
import { render, screen } from '@testing-library/react';
import App from './App';
/**
* STEP 1: import and mock the useSWR
**/
import useSWR from 'swr';
jest.mock('swr');
test('renders learn react link', async () => {
// STEP 2: implement the mock return value of useSWR
useSWR.mockImplementation(() => ({
data: [
{
userId: 1,
id: 1,
title: 'foobar',
completed: true,
},
],
error: null,
isValidating: false,
mutate: null,
}));
render(<App />);
const header = await screen.findByRole('heading', { name: 'Tasks' });
const todos = await screen.findByTestId('todos');
expect(header).toBeInTheDocument();
expect(todos.children.length).toBe(200);
});
方法二
import { render, screen } from '@testing-library/react';
import App from './App';
// STEP 1: import and set the SWR.default as jest mock function
import * as SWR from 'swr';
SWR.default = jest.fn();
test('renders learn react link', async () => {
// STEP 2: mock the return value of SWR.default
SWR.default.mockImplementation(() => ({
data: [
{
userId: 1,
id: 1,
title: 'foobar',
completed: true,
},
],
error: null,
isValidating: false,
mutate: null,
}));
render(<App />);
const header = await screen.findByRole('heading', { name: 'Tasks' });
const todos = await screen.findByTestId('todos');
expect(header).toBeInTheDocument();
expect(todos.children.length).toBe(200);
});
mock react-i18n, next-i18next 的 useTranslation
建立 globalMock.js
,並且 mock next-i18next
中的 useTranslation
方法:
// internal/mocks/globalMock.js
// 等同於
// jest.mock('next-i18next')
// next.i18next.mockImplementation(() => ({ useTranslation: {...}}))
jest.mock('next-i18next', () => ({
useTranslation: () => ({
t: jest.fn().mockImplementation((key) => key),
i18n: { language: 'en-us' },
}),
}));
也可以建立 global 的 mock function,只需在 jest.config.js
中,透過 setupFiles
來載入這隻檔案:
// jest.config.js
module.exports = {
setupFiles: ['<rootDir>/internal/mocks/globalMock.js'],
};
測試 AJAX 的 API
::: tip 要檢驗有沒有成功 mock 某個 package 或 function,最簡單的方式就是到使用此 Module 的檔案去 console 查看輸出的結果。舉例來說,如果是要 mocked Todo 元件中的 axios,就在寫好 mock 之後,到 Todo 元件中看 axios 回傳的結果即可。 :::
在 create-react-app 中可以使用下面的方式:
// app.test.js
const mockResponse = {
data: {
results: ['Aaron', 'Andy'],
},
};
// 等同於
// jest.mock('axios')
// axios.mockImplementation(() => ({get: () => ...}))
jest.mock('axios', () => {
return {
get: () => Promise.resolve(mockResponse),
};
});
test('should return a user', () => {
// do what you want, the axios in MockFollowerList will be mocked
render(<MockFollowerList />);
const followerItems = await screen.findAllByTestId(/follower-item/);
expect(followerItems.length).toBe(1);
});
或者如果也可以建立 __mocks__
的資料夾,Jest 會自動將檔案名稱與套件名稱做對應:
// __mocks__/axios.js
const mockResponse = {
data: {
results: ['Aaron', 'Andy'],
},
};
const mockAxios = {
get: () => Promise.resolve(mockResponse),
};
export default mockAxios;
如果是在 Node.js 的環境,則可以這樣做:
The only 3 steps you need to mock an API call in Jest @ dev.to
// index.test.js
const getFirstAlbumTitle = require('./index');
const axios = require('axios');
jest.mock('axios');
it('returns the title of the first album', async () => {
axios.get.mockResolvedValue({
data: [
{
userId: 1,
id: 1,
title: 'My First Album',
},
{
userId: 1,
id: 2,
title: 'Album: The Sequel',
},
],
});
const title = await getFirstAlbumTitle();
expect(title).toEqual('My First Album');
});
測試 element 上的 style
直接使用 document.body.style
即可:
it('should not add hidden to body after unmount', () => {
const initialOverflow = document.body.style.overflow;
expect(initialOverflow).not.toBe('hidden');
const { unmount } = renderHook((shouldBlockScroll) => useBlockDocumentScroll(shouldBlockScroll), {
initialProps: true,
});
expect(document.body.style.overflow).toBe('hidden');
unmount();
expect(document.body.style.overflow).toBe(initialOverflow);
});
測試中會用到 localStorage
建立 localStorage 的 mock
// __mocks__/localStorage.mock.ts
const localStorageMock = (() => {
let store: Record<string, string> = {};
return {
getItem(key: string) {
return store[key] || null;
},
setItem(key: string, value: string) {
store[key] = value.toString();
},
clear() {
store = {};
},
};
})();
Object.defineProperty(window, 'localStorage', {
value: localStorageMock,
});
export {};
使用 localStorage 的 mock 進行測試
- 在
afterEach()
中,使用localStorage.clear()
把 localStorage 清空
import './__mocks__/localStorage.mock';
// eslint-disable-next-line import/first
import { act, renderHook } from '@testing-library/react-hooks';
import useLocalStorage from './useLocalStorage';
describe('useLocalStorage', () => {
afterEach(() => {
localStorage.clear();
});
it('should return the value from localStorage', () => {
const { result } = renderHook(() =>
useLocalStorage({ keyName: 'username', initialValue: 'Bob' }),
);
const [username, setUsername] = result.current;
expect(username).toBe('Bob');
act(() => {
setUsername('Aaron');
});
const [updatedUsername] = result.current;
expect(updatedUsername).toBe('Aaron');
});
it('if null is set, should get value of null but not "null"', () => {
const { result } = renderHook(() =>
useLocalStorage<string | null>({ keyName: 'username', initialValue: null }),
);
const [username, setUsername] = result.current;
expect(username).toBeNull();
expect(username).not.toBe('null');
act(() => {
setUsername('Aaron');
});
const [updatedUsername] = result.current;
expect(updatedUsername).toBe('Aaron');
});
});