2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Vitestで学ぶテストの基礎 〜モック編〜

Last updated at Posted at 2025-09-14

はじめに

Vitestでテストを書いていてモックの使い方に悩むことが多かったので、整理の意味も込めて Vitestにおけるモックの基礎をまとめてみました。

テストの基本的な考え方(describeit、フックなど)については、以前まとめたこちらの記事をご覧ください。

モックとは?

テストを書くときに、本物の関数や外部サービス(APIやDB、外部ライブラリなど)を利用すると困ることがあります。

  • 実行に時間がかかる
  • ネットワークやDBが必要で不安定になる
  • 本物の処理を呼ぶと副作用(データ更新など)が起きてしまう

こうした「テストの邪魔になる依存」を切り離して、代わりに用意するダミーの部品を「モック」と言い、モックを使うことで、テストを以下のようにシンプルにできます。

  • 早い(余計な処理をスキップできる)
  • 安全(本番のDBやAPIに影響しない)
  • コントロールしやすい(返す値や動きを自由に決められる)

スタブ・スパイとの違い

モックと似た言葉にスタブスパイがあります。
厳密な区別はありますが、ざっくり以下のような役割を持ちます。

  • スタブ:「呼ばれたら決まった値を返す」
  • スパイ:「呼ばれた回数や引数を記録する」
  • モック:「スタブのように値を返すこともでき、スパイのように呼び出しを確認することもできる」

普段の会話では上記をすべてまとめて「モック」と呼ぶことが多く、Vitest では vi.fn()vi.spyOn() を使うことで、こうした動きを簡単に再現できます。

Vitestでのモックの基本

vi.fn()

新しく「モック関数」を作るための仕組みです。
呼び出し履歴を記録したり、返り値を自由に設定したりできる、一番基本的なモックの使い方です。

使い方の例

const mockFn = vi.fn()               // モック関数を作る
mockFn("hello")                      // 呼び出し

expect(mockFn).toHaveBeenCalled()    // 呼ばれたことを確認
expect(mockFn).toHaveBeenCalledWith("hello") // 引数を確認

返り値を設定することもできます👇

const mockFn = vi.fn().mockReturnValue("fixed value")
expect(mockFn()).toBe("fixed value")

制限・注意点

  • 元の関数を置き換えるわけではない
    vi.fn() はまっさらな関数を作るだけなので、既存の関数を差し替えたい場合は vi.spyOn を使う
  • 呼ばれ方の記録はテスト間で共有されない
    → 各テストごとにモック関数を作り直すのが基本

実際に使う場面

「自分でダミー関数を用意して、呼び出しを記録・検証したい」ときに使います。

  • 外部から受け取る関数をテスト用に置き換える
    function runTask(callback: (msg: string) => void) {
      callback("task done")
    }
    
    const mockFn = vi.fn()
    runTask(mockFn)
    
    expect(mockFn).toHaveBeenCalledWith("task done")
    
  • 関数の呼び出し回数や引数を検証する
    • API 呼び出し関数を渡したときに、正しい引数で呼ばれているかをチェック
    • ボタン押下時にイベントハンドラが呼ばれているかをチェック

vi.spyOn()

既存のオブジェクトや関数を「監視」したり「差し替え」したりするために使います。
vi.fn() は自分で新しくモック関数を作りますが、vi.spyOn() は既存の関数をモック化する点が違います。

使い方の例

vi.spyOn(対象オブジェクト, "監視したい関数名") という書き方で利用します。

const user = {
  getName: () => "Alice",
};

// getName を spyOn で監視対象にする
const spy = vi.spyOn(user, "getName")

// 返り値を「Bob」に差し替える
spy.mockReturnValue("Bob")

expect(user.getName()).toBe("Bob"); // 差し替えた返り値を確認
expect(spy).toHaveBeenCalled(); // モック関数が呼ばれたことを確認

// 元に戻す
spy.mockRestore()

制限・注意点

  • 監視できるのはオブジェクトのメソッドだけ
    • 例: console.log, Date.now
    • グローバル関数やトップレベルの関数は直接 spyOn できない
  • 他のテストに影響することがあるためテスト後は元に戻す

実際に使う場面

「既存の関数の動きを一時的に差し替えたいとき」に便利です。

  • ログ出力が呼ばれたか確認する
    const spy = vi.spyOn(console, "log")
    console.log("Hello")
    expect(spy).toHaveBeenCalledWith("Hello")
    spy.mockRestore()
    
  • 現在時刻を固定する
    const spy = vi.spyOn(Date, "now").mockReturnValue(123456)
    expect(Date.now()).toBe(123456)
    spy.mockRestore()
    

vi.mock()

モジュールをまるごと差し替えるときに使います。

使い方の例

  • 自動モック:モジュールがエクスポートしている関数をすべて vi.fn() に置き換える
    vi.mock("./api")
    
  • 手動モック:どうモックするか自分で定義
    vi.mock("./api", () => {
      return {
        fetchUser: vi.fn(() => Promise.resolve({ id: 1, name: "Alice" }))
      }
    })
    

制限・ルール

  • モックはファイルの先頭でしか使えない
    • Vitest がテストを読み込むときに、自動的にファイルの先頭に持ち上げて処理するので、testbeforeEach の中に書いても、その場では動かない
  • 条件分岐の中では使えない
    if (process.env.NODE_ENV === "test") {
      vi.mock("./api") // ❌ こういう条件分岐は効かない
    }
    
  • テストごとに切り替えられない
    • 「テストAではこのモック、テストBでは別のモック」といった切り替えは基本できない
      → どうしても切り替えたい場合は vi.doMock を使う方法がある
  • モックはファイルごとに有効
    • 一度モックすると、そのテストファイルの中ではずっとそのモックが使われる
    • 別のテストファイルには影響しない

実際に使う場面

  • 外のサービスにアクセスする処理を置き換える
    • 例: ユーザー情報をサーバーから取得する関数(API呼び出し)
  • 本物のデータベースにつなぐ処理を止める
    • 例: 実際のDBに書き込みたくないので、代わりのモックを使う
  • 本物の認証サービスを呼ばないようにする
    • 例: ログインチェックやトークン取得をテスト用の関数に差し替える

モック利用の考え方

書きたいテストによって使い分けることが大前提ですが、モックは小さい単位から順に使うように考えるのが基本です。

  • vi.fn: 新しいモック関数を作る(まずはこれで十分なことが多い)
  • vi.spyOn: 既存の関数を監視・差し替えたいとき
  • vi.mock: モジュール全体を丸ごとモックしたいとき(最後の手段)

参考

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?