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