跳至主要内容

[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');
});
});

Discuss