Jest と Vitest で「関数内で呼び出される関数の戻り値をモックする」方法を比べてみた

2023.09.04

こんにちは、CX事業本部 Delivery部の若槻です。

今まで JavaScript テスティングフレームワークとしてずっと Jest を使っていましたが、最近は社内でおすすめされて Vitest を使い始めています。多くのマッチャーや API が Jest と互換性があり、また実行速度が速いのが特徴です。

さて今まで Jest で関数のユニットテストを実装する際に「関数内で呼び出される関数の戻り値をモック」ということをやってきたのですが、これが Vitest ではどうなるのか、方法を比べてみました。

やってみた

関数を実装したモジュールです。

以下は呼び出す側の関数callerを実装したモジュールmoduleAです。

src/moduleB.ts

import { called } from './moduleB';

export const caller = () => called(2);

以下は呼び出される側の関数calledを実装したモジュールmoduleBです。

src/moduleA.ts

export const called = (max: number) => Math.floor(Math.random() * max);

呼び出す側の関数のユニットテストを Jest と Vitest で実装してみます。

Jest の場合

環境

jest.config.js

module.exports = {
  testEnvironment: 'node',
  roots: ['<rootDir>/src'],
  testMatch: ['**/*.test.ts'],
  transform: {
    '^.+\\.tsx?$': 'ts-jest',
  },
};
$ npm ls jest
cdk_sample_app@0.1.0 /Users/wakatsuki.ryuta/projects/cm-rwakatsuki/cdk_sample_app
├── jest@29.6.4
└─┬ ts-jest@29.1.1
  └── jest@29.6.4 deduped

テストコード

Jest の場合は次のようになります。jest.fn()でモックしたい関数をモック関数とします。

src/moduleA-jest.test.ts

import { caller } from './moduleA';
import { called } from './moduleB';

test('moduleA', (): void => {
  (called as jest.Mock) = jest.fn().mockReturnValue(5);

  const result = caller();

  expect(called).toHaveBeenCalledTimes(1);
  expect(called).toBeCalledWith(2);

  expect(result).toBe(7);
});

テストコードを Jest で実行した様子です。

$ npx jest moduleA-jest.test.ts
 PASS  src/moduleA-jest.test.ts
  ✓ moduleA (2 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        1.988 s, estimated 2 s
Ran all test suites matching /moduleA-jest.test.ts/i.

ちなみにトータルの実行時間は 1.988 s です。

Vitest の場合

環境

$ npm ls vitest
cdk_sample_app@0.1.0 /Users/wakatsuki.ryuta/projects/cm-rwakatsuki/cdk_sample_app
└── vitest@0.34.3

テストコード

Vitest の場合は次のようになります。まずvi.mock()でモジュールごとモックし、vi.fn()でモックしたい関数をモック関数とします。fn()には下記のmockReturnValueの他にmockResolvedValuemockReturnValueOnceなど Jest と同じものは一通り使えます。

src/moduleA-vitest.test.ts

import { caller } from './moduleA';
import { called } from './moduleB';

import { expect, test, vi } from 'vitest';

vi.mock('./moduleB', () => {
  return {
    called: vi.fn().mockReturnValue(5),
  };
});

test('moduleA', (): void => {
  const result = caller();

  expect(called).toHaveBeenCalledTimes(1);
  expect(called).toBeCalledWith(2);

  expect(result).toBe(7);
});

テストコードを Vitest で実行した様子です。

$ npx vitest run moduleA-vitest.test.ts

 RUN  v0.34.3 /Users/wakatsuki.ryuta/projects/cm-rwakatsuki/cdk_sample_app

 ✓ src/moduleA-vitest.test.ts (1)
   ✓ moduleA

 Test Files  1 passed (1)
      Tests  1 passed (1)
   Start at  01:35:07
   Duration  252ms (transform 54ms, setup 0ms, collect 48ms, tests 2ms, environment 0ms, prepare 49ms)

トータルの実行時間は 252ms です。Jest と比べて 1/8 くらいの時間です。さすがトップページで「Blazing Fast Unit Test Framework」と謳っているだけあって速いですね。

おわりに

Jest と Vitest で「関数内で呼び出される関数の戻り値をモックする」方法を比べてみました。

マッチャーは基本同じなのですが、このモック部分は異なっていたので少し調査する必要がありました。引き続き Vitest を使って慣れていきたいと思います。

参考

以上