T-CREATOR

Vitest `vi` API 技術チートシート:`mock` / `fn` / `spyOn` / `advanceTimersByTime` 一覧

Vitest `vi` API 技術チートシート:`mock` / `fn` / `spyOn` / `advanceTimersByTime` 一覧

Vitest の vi API は、テスト駆動開発において欠かせない強力なモック・スパイ機能を提供しています。関数の呼び出しを監視したり、外部モジュールの動作を制御したり、時間の流れそのものを操作することで、複雑な非同期処理やタイマー処理も確実にテストできるようになります。

この記事では、vi API の中でも特に使用頻度の高い mockfnspyOnadvanceTimersByTime を中心に、各 API の使い方を具体例とともに詳しく解説していきます。

API 早見表

#API 名カテゴリ主な用途戻り値
1vi.fn()モック関数スパイ可能な関数を作成Mock<T>
2vi.spyOn()スパイ既存メソッドの呼び出しを監視MockInstance<T>
3vi.mock()モジュールモックモジュール全体をモック化void
4vi.doMock()モジュールモック動的にモジュールをモック化void
5vi.mocked()型アサーションモック済みオブジェクトの型を取得MaybeMockedDeep<T>
6vi.clearAllMocks()リセットすべてのモックの呼び出し履歴をクリアvoid
7vi.resetAllMocks()リセットすべてのモックを初期状態に戻すvoid
8vi.restoreAllMocks()リストアすべてのモックを元の実装に戻すvoid
9vi.useFakeTimers()タイマータイマーをモック化void
10vi.useRealTimers()タイマータイマーを実際の動作に戻すvoid
11vi.advanceTimersByTime()タイマータイマーを指定ミリ秒進めるvoid
12vi.advanceTimersToNextTimer()タイマー次のタイマーまで進めるvoid
13vi.runAllTimers()タイマーすべてのタイマーを実行void
14vi.setSystemTime()時刻システム時刻を設定void
15vi.getRealSystemTime()時刻実際のシステム時刻を取得number

背景

テストを書く際、すべての依存関係を実際に動かすのは現実的ではありません。たとえば API 呼び出し、データベースアクセス、ファイル I/O などの外部依存は、テスト実行のたびに時間がかかり、環境によっては失敗する可能性もあります。

そこで登場するのが「モック」と「スパイ」の概念です。モックは本物の関数やモジュールの代わりに動作するダミーで、スパイは既存の関数の動作を監視する仕組みです。これらを活用することで、テストの実行速度を大幅に向上させ、外部環境に依存しない安定したテストを実現できます。

以下の図は、実際の依存関係とテスト時のモック化された依存関係の違いを示しています。

mermaidflowchart LR
  subgraph real["本番環境"]
    app1["アプリケーション"] -->|HTTP リクエスト| api1["外部 API"]
    app1 -->|DB クエリ| db1[("データベース")]
  end

  subgraph test["テスト環境"]
    app2["アプリケーション"] -->|モック呼び出し| mock_api["モック API"]
    app2 -->|モック呼び出し| mock_db["モック DB"]
  end

実際の環境では外部 API やデータベースと通信しますが、テスト環境ではこれらをモックに置き換えることで、高速かつ安定したテストが可能になります。

Vitest の vi API は、こうしたモック・スパイ機能を直感的かつ柔軟に実装するための強力なツールセットです。Jest の jest オブジェクトと互換性があるため、既存の Jest テストからの移行もスムーズに行えます。

課題

テストを書く際に直面する代表的な課題には、以下のようなものがあります。

外部依存の制御が困難

外部 API やデータベースへの実際の接続は、テストの実行速度を遅くし、ネットワークの状態やサーバーの応答に依存するため不安定になりがちです。また、テスト実行のたびに外部リソースを消費するため、CI/CD 環境でのコスト増加にもつながります。

関数の呼び出し履歴を追跡できない

関数が正しく呼ばれたか、何回呼ばれたか、どんな引数で呼ばれたかを確認するには、何らかの監視機構が必要です。通常の関数呼び出しでは、こうした情報を取得することができません。

時間依存の処理をテストできない

setTimeoutsetIntervalDate.now() などの時間依存の処理は、実際の時間が経過するのを待つ必要があり、テストの実行時間が長くなります。また、時刻に依存するテストは環境によって結果が変わる可能性があるため、再現性の低いテストになりがちです。

以下の図は、これらの課題がテスト品質に与える影響を示しています。

mermaidflowchart TD
  start["テスト実行"] --> check1{"外部依存<br/>制御可能?"}
  check1 -->|No| slow["テスト遅延<br/>不安定化"]
  check1 -->|Yes| check2{"呼び出し履歴<br/>追跡可能?"}
  check2 -->|No| incomplete["検証不十分"]
  check2 -->|Yes| check3{"時間制御<br/>可能?"}
  check3 -->|No| slow
  check3 -->|Yes| success["高速で<br/>安定したテスト"]

これらの課題を解決するために、Vitest は vi API という強力なモック・スパイ機構を提供しています。

解決策

Vitest の vi API は、上記の課題を解決するための包括的な機能を提供します。主な解決策は以下の 3 つに分類できます。

モック関数による依存の置き換え

vi.fn() を使うことで、スパイ可能な関数を作成できます。この関数は呼び出し履歴を記録し、戻り値を自由に設定できます。また、vi.mock() を使えば、モジュール全体をモック化し、外部依存を完全に制御できます。

スパイによる関数監視

vi.spyOn() を使うことで、既存のオブジェクトのメソッドを監視できます。元の実装を保持したまま呼び出し履歴を記録したり、必要に応じて実装を置き換えたりすることが可能です。

タイマー制御による時間操作

vi.useFakeTimers() でタイマーをモック化し、vi.advanceTimersByTime() で時間を進めることで、実際の時間経過を待たずに時間依存の処理をテストできます。

以下の図は、vi API がこれらの課題をどのように解決するかを示しています。

mermaidflowchart LR
  subgraph challenges["課題"]
    c1["外部依存の制御"]
    c2["呼び出し履歴の追跡"]
    c3["時間依存の処理"]
  end

  subgraph solutions["vi API による解決"]
    s1["vi.fn()<br/>vi.mock()"]
    s2["vi.spyOn()"]
    s3["vi.useFakeTimers()<br/>vi.advanceTimersByTime()"]
  end

  c1 --> s1
  c2 --> s2
  c3 --> s3

  s1 --> result["高速・安定・<br/>検証可能な<br/>テスト"]
  s2 --> result
  s3 --> result

次のセクションでは、これらの API を具体的なコード例とともに詳しく見ていきましょう。

具体例

ここでは、Vitest の vi API を使った具体的なテストコードを紹介します。

vi.fn() - モック関数の作成

vi.fn() は、スパイ可能な関数を作成するための基本的な API です。呼び出し回数、引数、戻り値などを自由に制御・検証できます。

以下のコードは、vi.fn() でモック関数を作成し、戻り値を設定する例です。

typescriptimport { describe, it, expect, vi } from 'vitest';

describe('vi.fn() の基本', () => {
  it('モック関数を作成し、呼び出しを検証できる', () => {
    // モック関数を作成
    const mockFn = vi.fn();

    // 関数を呼び出す
    mockFn('hello', 123);

    // 呼び出しを検証
    expect(mockFn).toHaveBeenCalledTimes(1);
    expect(mockFn).toHaveBeenCalledWith('hello', 123);
  });
});

vi.fn() で作成した関数は、toHaveBeenCalledTimestoHaveBeenCalledWith といったマッチャーで呼び出しを検証できます。

次に、戻り値を設定する方法を見てみましょう。

typescriptimport { describe, it, expect, vi } from 'vitest';

describe('vi.fn() の戻り値設定', () => {
  it('mockReturnValue で戻り値を設定できる', () => {
    const mockFn = vi.fn().mockReturnValue(42);

    // 関数を呼び出すと、設定した値が返る
    const result = mockFn();

    expect(result).toBe(42);
    expect(mockFn).toHaveBeenCalledTimes(1);
  });
});

mockReturnValue を使うことで、モック関数が呼ばれたときに返す値を設定できます。

複数回の呼び出しで異なる値を返したい場合は、mockReturnValueOnce を使います。

typescriptimport { describe, it, expect, vi } from 'vitest';

describe('vi.fn() の複数回呼び出し', () => {
  it('mockReturnValueOnce で呼び出しごとに異なる値を返せる', () => {
    const mockFn = vi
      .fn()
      .mockReturnValueOnce('first')
      .mockReturnValueOnce('second')
      .mockReturnValue('default');

    // 1回目の呼び出し
    expect(mockFn()).toBe('first');
    // 2回目の呼び出し
    expect(mockFn()).toBe('second');
    // 3回目以降はデフォルト値
    expect(mockFn()).toBe('default');
    expect(mockFn()).toBe('default');
  });
});

mockReturnValueOnce は、指定した回数だけ値を返し、それ以降は mockReturnValue で設定した値を返します。

非同期関数をモックする場合は、mockResolvedValue を使います。

typescriptimport { describe, it, expect, vi } from 'vitest';

describe('vi.fn() の非同期モック', () => {
  it('mockResolvedValue で Promise を返すモックを作成できる', async () => {
    const mockAsyncFn = vi
      .fn()
      .mockResolvedValue({ id: 1, name: 'Alice' });

    // 非同期関数を呼び出す
    const result = await mockAsyncFn();

    expect(result).toEqual({ id: 1, name: 'Alice' });
    expect(mockAsyncFn).toHaveBeenCalledTimes(1);
  });
});

mockResolvedValue は、Promise を返すモック関数を簡単に作成できます。エラーをシミュレートする場合は、mockRejectedValue を使います。

vi.spyOn() - 既存メソッドの監視

vi.spyOn() は、既存のオブジェクトのメソッドを監視し、呼び出し履歴を記録します。元の実装を保持したまま監視することも、実装を置き換えることも可能です。

以下のコードは、vi.spyOn() で既存メソッドを監視する例です。

typescriptimport { describe, it, expect, vi } from 'vitest';

const calculator = {
  add: (a: number, b: number) => a + b,
  multiply: (a: number, b: number) => a * b,
};

describe('vi.spyOn() の基本', () => {
  it('既存メソッドの呼び出しを監視できる', () => {
    // add メソッドにスパイを設定
    const spy = vi.spyOn(calculator, 'add');

    // 実際にメソッドを呼び出す
    const result = calculator.add(2, 3);

    // 元の実装が動作している
    expect(result).toBe(5);
    // 呼び出しが記録されている
    expect(spy).toHaveBeenCalledTimes(1);
    expect(spy).toHaveBeenCalledWith(2, 3);
  });
});

vi.spyOn() は、元の実装を保持したまま呼び出しを監視します。実装を置き換えたい場合は、mockImplementation を使います。

typescriptimport { describe, it, expect, vi } from 'vitest';

const calculator = {
  add: (a: number, b: number) => a + b,
};

describe('vi.spyOn() の実装置き換え', () => {
  it('mockImplementation で実装を置き換えられる', () => {
    const spy = vi
      .spyOn(calculator, 'add')
      .mockImplementation(() => 100);

    // 置き換えた実装が呼ばれる
    const result = calculator.add(2, 3);

    expect(result).toBe(100);
    expect(spy).toHaveBeenCalledWith(2, 3);
  });
});

mockImplementation を使うことで、元の実装を完全に置き換えることができます。

スパイを元に戻したい場合は、mockRestore を使います。

typescriptimport { describe, it, expect, vi } from 'vitest';

const calculator = {
  add: (a: number, b: number) => a + b,
};

describe('vi.spyOn() のリストア', () => {
  it('mockRestore で元の実装に戻せる', () => {
    const spy = vi
      .spyOn(calculator, 'add')
      .mockImplementation(() => 100);

    // モック実装が動作
    expect(calculator.add(2, 3)).toBe(100);

    // 元の実装に戻す
    spy.mockRestore();

    // 元の実装が復元される
    expect(calculator.add(2, 3)).toBe(5);
  });
});

mockRestore を使うことで、スパイを解除し、元のメソッドの実装に戻すことができます。

vi.mock() - モジュール全体のモック化

vi.mock() は、モジュール全体をモック化するための API です。外部ライブラリや別ファイルのモジュールを完全に制御できます。

以下のコードは、vi.mock() でモジュール全体をモック化する例です。

まず、モック対象のモジュールを定義します。

typescript// src/api.ts
export async function fetchUser(id: number) {
  const response = await fetch(
    `https://api.example.com/users/${id}`
  );
  return response.json();
}

export function getApiUrl() {
  return 'https://api.example.com';
}

次に、テストコードでこのモジュールをモック化します。

typescriptimport { describe, it, expect, vi } from 'vitest';
import { fetchUser, getApiUrl } from '../src/api';

// モジュール全体をモック化
vi.mock('../src/api', () => ({
  fetchUser: vi
    .fn()
    .mockResolvedValue({ id: 1, name: 'Alice' }),
  getApiUrl: vi
    .fn()
    .mockReturnValue('https://mock.example.com'),
}));

describe('vi.mock() でモジュールをモック化', () => {
  it('モジュールの関数がモック化される', async () => {
    // モック化された関数が呼ばれる
    const user = await fetchUser(1);
    const url = getApiUrl();

    expect(user).toEqual({ id: 1, name: 'Alice' });
    expect(url).toBe('https://mock.example.com');
    expect(fetchUser).toHaveBeenCalledWith(1);
  });
});

vi.mock() を使うことで、モジュール全体をモック化し、すべてのエクスポートを制御できます。

一部の関数だけをモック化し、他の関数は元の実装を使いたい場合は、vi.importActual() を併用します。

typescriptimport { describe, it, expect, vi } from 'vitest';
import { fetchUser, getApiUrl } from '../src/api';

// 一部だけモック化
vi.mock('../src/api', async () => {
  const actual = await vi.importActual<
    typeof import('../src/api')
  >('../src/api');
  return {
    ...actual,
    fetchUser: vi
      .fn()
      .mockResolvedValue({ id: 1, name: 'Alice' }),
  };
});

describe('vi.mock() の部分モック化', () => {
  it('一部の関数だけモック化できる', async () => {
    const user = await fetchUser(1);
    const url = getApiUrl();

    // fetchUser はモック化されている
    expect(user).toEqual({ id: 1, name: 'Alice' });
    // getApiUrl は元の実装が動作
    expect(url).toBe('https://api.example.com');
  });
});

vi.importActual() を使うことで、元のモジュールの実装を取得し、一部だけをモック化できます。

vi.mocked() - モックの型アサーション

vi.mocked() は、モック化されたオブジェクトの型を正しく取得するためのヘルパー関数です。TypeScript でモックを扱う際に便利です。

以下のコードは、vi.mocked() を使ってモックの型を取得する例です。

typescriptimport { describe, it, expect, vi } from 'vitest';
import { fetchUser } from '../src/api';

vi.mock('../src/api', () => ({
  fetchUser: vi.fn(),
}));

describe('vi.mocked() の型アサーション', () => {
  it('モックの型を正しく取得できる', async () => {
    // vi.mocked() で型アサーション
    const mockFetchUser = vi.mocked(fetchUser);

    // TypeScript が mockResolvedValue を認識する
    mockFetchUser.mockResolvedValue({ id: 2, name: 'Bob' });

    const user = await fetchUser(2);

    expect(user).toEqual({ id: 2, name: 'Bob' });
    expect(mockFetchUser).toHaveBeenCalledWith(2);
  });
});

vi.mocked() を使うことで、TypeScript がモックのメソッドを正しく認識し、補完やエラーチェックが効きます。

vi.useFakeTimers()vi.advanceTimersByTime() - タイマーの制御

vi.useFakeTimers() は、setTimeoutsetIntervalDate などのタイマー関連 API をモック化します。vi.advanceTimersByTime() を使うことで、実際の時間経過を待たずにタイマーを進めることができます。

以下のコードは、vi.useFakeTimers()vi.advanceTimersByTime() を使ってタイマーを制御する例です。

まず、タイマーを使う関数を定義します。

typescript// src/timer.ts
export function delayedGreeting(
  callback: (message: string) => void
) {
  setTimeout(() => {
    callback('Hello after 1 second');
  }, 1000);
}

次に、テストコードでタイマーを制御します。

typescriptimport {
  describe,
  it,
  expect,
  vi,
  beforeEach,
  afterEach,
} from 'vitest';
import { delayedGreeting } from '../src/timer';

describe('vi.useFakeTimers() と vi.advanceTimersByTime()', () => {
  beforeEach(() => {
    // テスト前にタイマーをモック化
    vi.useFakeTimers();
  });

  afterEach(() => {
    // テスト後にタイマーを元に戻す
    vi.useRealTimers();
  });

  it('タイマーを進めてコールバックを実行できる', () => {
    const callback = vi.fn();

    // タイマーを設定
    delayedGreeting(callback);

    // まだコールバックは呼ばれていない
    expect(callback).not.toHaveBeenCalled();

    // 1秒進める
    vi.advanceTimersByTime(1000);

    // コールバックが呼ばれた
    expect(callback).toHaveBeenCalledTimes(1);
    expect(callback).toHaveBeenCalledWith(
      'Hello after 1 second'
    );
  });
});

vi.useFakeTimers() でタイマーをモック化し、vi.advanceTimersByTime(1000) で 1 秒進めることで、実際の時間を待たずにタイマーをテストできます。

複数のタイマーを一度に実行したい場合は、vi.runAllTimers() を使います。

typescriptimport {
  describe,
  it,
  expect,
  vi,
  beforeEach,
  afterEach,
} from 'vitest';

function multipleTimers(
  callback: (message: string) => void
) {
  setTimeout(() => callback('First'), 1000);
  setTimeout(() => callback('Second'), 2000);
  setTimeout(() => callback('Third'), 3000);
}

describe('vi.runAllTimers()', () => {
  beforeEach(() => {
    vi.useFakeTimers();
  });

  afterEach(() => {
    vi.useRealTimers();
  });

  it('すべてのタイマーを一度に実行できる', () => {
    const callback = vi.fn();

    multipleTimers(callback);

    // すべてのタイマーを実行
    vi.runAllTimers();

    // すべてのコールバックが呼ばれた
    expect(callback).toHaveBeenCalledTimes(3);
    expect(callback).toHaveBeenNthCalledWith(1, 'First');
    expect(callback).toHaveBeenNthCalledWith(2, 'Second');
    expect(callback).toHaveBeenNthCalledWith(3, 'Third');
  });
});

vi.runAllTimers() は、すべてのタイマーを即座に実行します。

次のタイマーだけを実行したい場合は、vi.advanceTimersToNextTimer() を使います。

typescriptimport {
  describe,
  it,
  expect,
  vi,
  beforeEach,
  afterEach,
} from 'vitest';

function multipleTimers(
  callback: (message: string) => void
) {
  setTimeout(() => callback('First'), 1000);
  setTimeout(() => callback('Second'), 2000);
}

describe('vi.advanceTimersToNextTimer()', () => {
  beforeEach(() => {
    vi.useFakeTimers();
  });

  afterEach(() => {
    vi.useRealTimers();
  });

  it('次のタイマーまで進められる', () => {
    const callback = vi.fn();

    multipleTimers(callback);

    // 最初のタイマーまで進める
    vi.advanceTimersToNextTimer();
    expect(callback).toHaveBeenCalledTimes(1);
    expect(callback).toHaveBeenCalledWith('First');

    // 次のタイマーまで進める
    vi.advanceTimersToNextTimer();
    expect(callback).toHaveBeenCalledTimes(2);
    expect(callback).toHaveBeenCalledWith('Second');
  });
});

vi.advanceTimersToNextTimer() は、次のタイマーまで時間を進め、1 つずつタイマーを実行できます。

vi.setSystemTime() - システム時刻の設定

vi.setSystemTime() は、Date.now()new Date() が返す時刻を制御します。現在時刻に依存するコードをテストする際に便利です。

以下のコードは、vi.setSystemTime() を使ってシステム時刻を設定する例です。

まず、現在時刻に依存する関数を定義します。

typescript// src/time.ts
export function getCurrentYear() {
  return new Date().getFullYear();
}

export function isAfter2025() {
  return new Date().getFullYear() > 2025;
}

次に、テストコードでシステム時刻を設定します。

typescriptimport {
  describe,
  it,
  expect,
  vi,
  beforeEach,
  afterEach,
} from 'vitest';
import { getCurrentYear, isAfter2025 } from '../src/time';

describe('vi.setSystemTime()', () => {
  beforeEach(() => {
    vi.useFakeTimers();
  });

  afterEach(() => {
    vi.useRealTimers();
  });

  it('システム時刻を 2025年 に設定できる', () => {
    // 2025年1月1日に設定
    vi.setSystemTime(new Date('2025-01-01'));

    expect(getCurrentYear()).toBe(2025);
    expect(isAfter2025()).toBe(false);
  });

  it('システム時刻を 2026年 に設定できる', () => {
    // 2026年1月1日に設定
    vi.setSystemTime(new Date('2026-01-01'));

    expect(getCurrentYear()).toBe(2026);
    expect(isAfter2025()).toBe(true);
  });
});

vi.setSystemTime() を使うことで、テスト内の時刻を自由に設定し、現在時刻に依存するロジックを確実にテストできます。

モックのリセットとリストア

テストが終わったら、モックをリセットまたはリストアすることが重要です。以下の 3 つの API を使い分けます。

#API 名動作使用場面
1vi.clearAllMocks()呼び出し履歴だけをクリア次のテストで呼び出し回数を 0 からカウントしたい
2vi.resetAllMocks()実装と履歴を初期化モックの実装をリセットしたい
3vi.restoreAllMocks()元の実装に完全に戻すスパイを解除し、元のメソッドに戻したい

以下のコードは、これらの API を使い分ける例です。

typescriptimport {
  describe,
  it,
  expect,
  vi,
  beforeEach,
} from 'vitest';

const calculator = {
  add: (a: number, b: number) => a + b,
};

describe('モックのリセットとリストア', () => {
  it('vi.clearAllMocks() は呼び出し履歴だけをクリアする', () => {
    const spy = vi
      .spyOn(calculator, 'add')
      .mockReturnValue(100);

    calculator.add(1, 2);
    expect(spy).toHaveBeenCalledTimes(1);

    // 呼び出し履歴をクリア
    vi.clearAllMocks();

    // 実装は残っている
    expect(calculator.add(1, 2)).toBe(100);
    // 呼び出し回数は 0 にリセットされた
    expect(spy).toHaveBeenCalledTimes(1);
  });

  it('vi.resetAllMocks() は実装と履歴を初期化する', () => {
    const spy = vi
      .spyOn(calculator, 'add')
      .mockReturnValue(100);

    calculator.add(1, 2);
    expect(spy).toHaveBeenCalledTimes(1);

    // 実装と履歴をリセット
    vi.resetAllMocks();

    // 実装が undefined になる
    expect(calculator.add(1, 2)).toBeUndefined();
    // 呼び出し回数は 0 にリセットされた
    expect(spy).toHaveBeenCalledTimes(1);
  });

  it('vi.restoreAllMocks() は元の実装に完全に戻す', () => {
    const spy = vi
      .spyOn(calculator, 'add')
      .mockReturnValue(100);

    expect(calculator.add(1, 2)).toBe(100);

    // 元の実装に戻す
    vi.restoreAllMocks();

    // 元の実装が復元される
    expect(calculator.add(1, 2)).toBe(3);
  });
});

beforeEachafterEach でこれらの API を呼び出すことで、テストごとにモックの状態をリセットし、テスト間の依存を排除できます。

まとめ

Vitest の vi API は、テストの品質と実行速度を大幅に向上させる強力なツールセットです。この記事では、以下の主要 API について解説しました。

  • vi.fn(): モック関数を作成し、呼び出し履歴を記録・検証できる
  • vi.spyOn(): 既存メソッドを監視し、元の実装を保持したまま呼び出しを追跡できる
  • vi.mock(): モジュール全体をモック化し、外部依存を完全に制御できる
  • vi.mocked(): モックの型を正しく取得し、TypeScript での型補完を有効にできる
  • vi.useFakeTimers()vi.advanceTimersByTime(): タイマーをモック化し、時間を自由に操作できる
  • vi.setSystemTime(): システム時刻を設定し、現在時刻に依存するコードをテストできる
  • vi.clearAllMocks() / vi.resetAllMocks() / vi.restoreAllMocks(): モックをリセット・リストアし、テスト間の独立性を保つ

これらの API を適切に組み合わせることで、外部依存に左右されない高速かつ安定したテストを実現できます。モックとスパイを効果的に活用し、信頼性の高いテストコードを書いていきましょう。

関連リンク