【Vitest】モック機能の使い方

【Vitest】モック機能の使い方

Vitestが提供するモック機能は、テスト対象のコードをその依存関係から分離し、堅牢なテストを構築するために不可欠です。

Vitest #Vitest#モック#テスト

【Vitest】モック機能の使い方

サムネイル

Vitestが提供するモック機能は、テスト対象のコードをその依存関係から分離し、堅牢なテストを構築するために不可欠です。

更新日: 7/11/2025

基本的なモック - 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`);
  });
});

タイマーモック

setTimeoutsetIntervalといったタイマー関数に依存するコードは、通常テストが困難です。タイマーモックは、これらの関数を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);
  });
});

検索

検索条件に一致する記事が見つかりませんでした