基本的なモック - vi.fn()
vi.fn()
は汎用的なモック関数を作成する機能です。特定の関数の実装を偽の関数に置き換えたり、コールバック関数の振る舞いをシミュレートしたりする場合に役立ちます。
主な用途
- 関数の実装そのものを偽の関数に置き換える。
- 関数の戻り値を固定する。
- 関数が呼び出されたか、またどのような引数で呼び出されたかを検証する。
使用例
import { describe, it, expect, vi } from 'vitest';
function executeCallback(callback: (arg: string) => number) {
return callback('hello');
}
describe('executeCallback', () => {
it('should call the callback function and return its result', () => {
const mockCallback = vi.fn((_arg: string) => 42);
const result = executeCallback(mockCallback);
expect(result).toBe(42);
expect(mockCallback).toHaveBeenCalledTimes(1);
expect(mockCallback).toHaveBeenCalledWith('hello');
});
});
既存メソッドの監視 - vi.spyOn()
vi.spyOn()
は、既存オブジェクトが持つメソッドの本来の動作はそのままに、その呼び出しを監視したい場合に使用します。メソッドの元の実装を壊さずに、呼び出し履歴を追跡できるのが特徴です。
主な用途
- 特定のメソッドが呼び出されたことを確認したいが、その副作用(実際の処理)は実行させたい場合。
- テストの最後にもとの実装に戻したい場合(
mockRestore()
を使用)。
使用例
import { describe, it, expect, vi } from 'vitest';
const calculator = {
add(a: number, b: number) {
return a + b;
},
};
describe('calculator', () => {
it('should call the original add method', () => {
const addSpy = vi.spyOn(calculator, 'add');
const result = calculator.add(2, 3);
expect(result).toBe(5);
expect(addSpy).toHaveBeenCalledWith(2, 3);
addSpy.mockRestore();
});
});
モジュール全体のモック化 - vi.mock()
vi.mock()
は、特定のモジュール全体をモックに置き換えるための強力な機能です。外部ライブラリ(例: axios
)や、データベースクライアントのような自作モジュールへの依存を完全に断ち切りたい場合に不可欠です。
ISSUE - 課題
注意点
vi.mock()
は、ファイルのトップレベル(import
文の直後など)で呼び出す必要があります。
使用例
// src/utils/api.ts
import axios from 'axios';
export async function fetchUser(userId: number) {
const response = await axios.get(`https://api.example.com/users/${userId}`);
return response.data;
}
// src/utils/api.test.ts
import { describe, it, expect, vi } from 'vitest';
import { fetchUser } from './api';
import axios from 'axios';
vi.mock('axios');
describe('fetchUser', () => {
it('should fetch a user and return its data', async () => {
const mockUserData = { id: 1, name: 'Leanne Graham' };
vi.mocked(axios.get).mockResolvedValue({ data: mockUserData });
const user = await fetchUser(1);
expect(user).toEqual(mockUserData);
expect(axios.get).toHaveBeenCalledWith(`https://api.example.com/users/1`);
});
});
タイマーモック
setTimeout
やsetInterval
といったタイマー関数に依存するコードは、通常テストが困難です。タイマーモックは、これらの関数をVitestが制御できる偽のタイマーに置き換えることで、時間を待たずにテストを実行可能にします。
使用例
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
function debounce(callback: () => void, delay: number) {
let timerId: NodeJS.Timeout;
return () => {
clearTimeout(timerId);
timerId = setTimeout(callback, delay);
};
}
describe('debounce', () => {
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
});
it('should only call the callback after the delay', () => {
const callback = vi.fn();
const debounced = debounce(callback, 1000);
debounced();
debounced();
expect(callback).not.toHaveBeenCalled();
vi.advanceTimersByTime(1000);
expect(callback).toHaveBeenCalledTimes(1);
});
});