MeWrite Docs

Vitest: Mock function not being called

Vitestでモック関数が正しく動作しない場合の対処法

概要

Vitestでモック関数が呼び出されない、または期待通りに動作しない場合の対処法です。

エラーメッセージ

AssertionError: expected "spy" to be called 1 times, but got 0 times
Error: [vitest] Cannot mock "module" because it is already loaded
TypeError: vi.mock is not a function

解決策

1. 基本的なモック

1
2
3
4
5
6
7
8
import { describe, it, expect, vi } from 'vitest'

// 関数をモック
const mockFn = vi.fn()
mockFn()

expect(mockFn).toHaveBeenCalled()
expect(mockFn).toHaveBeenCalledTimes(1)

2. モジュールのモック

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// ファイルの先頭でモック(ホイスティングされる)
vi.mock('./api', () => ({
  fetchUser: vi.fn(() => Promise.resolve({ id: 1, name: 'John' })),
}))

import { fetchUser } from './api'

describe('User', () => {
  it('fetches user', async () => {
    const user = await fetchUser()
    expect(user.name).toBe('John')
  })
})

3. 動的モック

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import { vi, describe, it, beforeEach } from 'vitest'

// モックをインポート
vi.mock('./api')

import { fetchUser } from './api'

describe('User', () => {
  beforeEach(() => {
    vi.mocked(fetchUser).mockResolvedValue({ id: 1, name: 'Jane' })
  })

  it('fetches user', async () => {
    const user = await fetchUser()
    expect(user.name).toBe('Jane')
  })
})

4. 部分的なモック

1
2
3
4
5
6
7
8
vi.mock('./utils', async (importOriginal) => {
  const actual = await importOriginal()
  return {
    ...actual,
    // 特定の関数のみモック
    formatDate: vi.fn(() => '2025-01-01'),
  }
})

5. スパイ(既存関数を監視)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import { vi } from 'vitest'

const obj = {
  method: () => 'original',
}

const spy = vi.spyOn(obj, 'method')
spy.mockReturnValue('mocked')

expect(obj.method()).toBe('mocked')
expect(spy).toHaveBeenCalled()

// 元に戻す
spy.mockRestore()
expect(obj.method()).toBe('original')

6. タイマーのモック

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { vi, describe, it, beforeEach, afterEach } from 'vitest'

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

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

  it('delays execution', () => {
    const callback = vi.fn()
    setTimeout(callback, 1000)

    expect(callback).not.toHaveBeenCalled()

    vi.advanceTimersByTime(1000)

    expect(callback).toHaveBeenCalled()
  })
})

7. fetch/axiosのモック

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import { vi } from 'vitest'

// fetchをモック
global.fetch = vi.fn(() =>
  Promise.resolve({
    ok: true,
    json: () => Promise.resolve({ data: 'test' }),
  })
) as any

// axiosのモック
vi.mock('axios', () => ({
  default: {
    get: vi.fn(() => Promise.resolve({ data: { id: 1 } })),
    post: vi.fn(() => Promise.resolve({ data: { success: true } })),
  },
}))

8. 環境変数のモック

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import { vi, beforeEach, afterEach } from 'vitest'

describe('Environment', () => {
  const originalEnv = process.env

  beforeEach(() => {
    vi.resetModules()
    process.env = { ...originalEnv }
  })

  afterEach(() => {
    process.env = originalEnv
  })

  it('uses mocked env', async () => {
    process.env.API_KEY = 'test-key'
    const { getApiKey } = await import('./config')
    expect(getApiKey()).toBe('test-key')
  })
})

9. クリーンアップ

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import { vi, afterEach } from 'vitest'

afterEach(() => {
  // すべてのモックをリセット
  vi.clearAllMocks()

  // モック実装もリセット
  vi.resetAllMocks()

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

10. グローバル設定

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// vitest.config.ts
import { defineConfig } from 'vitest/config'

export default defineConfig({
  test: {
    globals: true,  // vi.をインポートなしで使用
    clearMocks: true,  // 各テスト後にモックをクリア
    mockReset: true,   // 各テスト後にモックをリセット
  },
})

関連エラー

関連エラー

最終更新: 2025-12-19