💡

Jestのモックパターン

2021/06/27に公開

Jest でモックする方法が色々あって毎回調べることになっているのでまとめておく

なお clearMocks オプションに true が設定されている前提です

副作用を止めるだけ

例えば以下

src/utils.ts
export const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
src/index.ts
import { sleep } from './utils';

export const test = async () => {
  await sleep(1000);
  // ...
  return 1;
};

戻り値のいらない sleep のような関数の副作用を止める場合はモジュールごとモックするだけで良い

__tests__/index.ts
import { test } from '../src';

// モジュールごとモック
jest.mock('../utils');

describe('test', () => {
  it('should return 1', async () => {
    expect(await test()).toBe(1);
  })
});

テスト全体で同じ動作

例えば以下

src/calc.ts
export const sum = (a: number, b: number) => a + b;
src/index.ts
import { sum } from './calc';

export const sumAll = (...values: Array<number>): number => {
  return values.reduce(sum, 0);
};

sum の動作をすべてのテストで同じ動作でモックする場合は以下のようにすれば良い

__tests__/index.ts
import { sumAll } from '../src';

// モジュールごとモック
jest.mock('../src/calc', () => ({
  sum: (a: number, b: number) => a + b + 1, // 動作を定義
}));

describe('sumAll', async () => {
  it('should return sum value', () => {
    expect(sumAll(1, 2, 3)).toBe(9); // 別の動作でモックしたので6ではなく9
  });
});

src/calc.ts に他の関数も存在し、それらはそのまま使用してほしい場合は requireActual を使えば良い

__tests__/index.ts
// モジュールごとモック
jest.mock('../src/calc', () => ({
  ...jest.requireActual<{}>('../src/calc'),
  sum: (a: number, b: number) => a + b + 1,
}));

テストごとに違う動作

前述の例で sum の動作をテストごとに異なる動作でモックする場合は以下のようにすれば良い

__tests__/index.ts
import { sumAll } from '../src';
import * as calc from '../src/calc';

describe('sumAll', async () => {
  it('should return sum value 1', () => {
    expect(sumAll(1, 2, 3)).toBe(6); // 元の動作のまま
  });

  it('should return sum value 2', () => {
    jest.spyOn(calc, 'sum').mockReturnValue(1); // sum は 1 を返す関数としてモック
    expect(sumAll(1, 2, 3)).toBe(1);
  });

  it('should return sum value 3', () => {
    jest.spyOn(calc, 'sum').mockImplementation((a, b) => a + b + 2); // 別の動作でモック
    expect(sumAll(1, 2, 3)).toBe(12);
  });
});

または以下

__tests__/index.ts
import { sumAll } from '../src';
import { sum } from '../src/calc';

jest.mock('../src/calc');

describe('sumAll', async () => {
  it('should return sum value 1', () => {
    (sum as jest.Mock).mockReturnValue(1); // sum は 1 を返す関数としてモック
    expect(sumAll(1, 2, 3)).toBe(1);
  });

  it('should return sum value 2', () => {
    (sum as jest.Mock).mockImplementation((a, b) => a + b + 2); // 別の動作でモック
    expect(sumAll(1, 2, 3)).toBe(12);
  });
});

関数呼び出しをテスト

前述の例で sum の呼ばれた回数や呼ばれたときの引数を確認したい場合は以下のようにすれば良い

__tests__/index.ts
import { sumAll } from '../src';
import * as calc from '../src/calc';

describe('sumAll', async () => {
  it('should return sum value', () => {
    const sum = jest.fn(() => 1);
    jest.spyOn(calc, 'sum').mockImplementation(sum);
    expect(sumAll(1, 2, 3)).toBe(1);
    expect(sum).toBeCalledTimes(3); // 3回呼ばれたことを確認
    expect(sum).lastCalledWith(1, 3, 2, [1, 2, 3]); // a = 1, b, index, original array
  });
});

Discussion