Vitestのモックについて
Vitest
vitestのモックについてまとめました.
Vitestのvi.fn()がなにをしているのか
const func = vi.fn(() => 5)
console.log(func)
func();
expect(func).toHaveBeenCalled()
expect(func).toHaveReturnedWith(5)
この場合、vi.fn()
は戻り値として5を返すモック関数を生成し、その関数をfunc
に代入しています。
vi.fn()
を使用すると、引数に指定した関数のモックを作成し、呼び出し回数や引数の記録、戻り値の変更などが可能になります。
console.log(func)
の結果は以下のようになります。
func.mockReturnValue
を使えば、戻り値を動的に変更することも可能です。
詳細は公式ドキュメントをご覧ください:
Vitest Mock Functions
[Function: spy] {
getMockName: [Function (anonymous)],
mockName: [Function (anonymous)],
mockClear: [Function (anonymous)],
mockReset: [Function (anonymous)],
mockRestore: [Function (anonymous)],
getMockImplementation: [Function (anonymous)],
mockImplementation: [Function (anonymous)],
mockImplementationOnce: [Function (anonymous)],
withImplementation: [Function: withImplementation],
mockReturnThis: [Function (anonymous)],
mockReturnValue: [Function (anonymous)],
mockReturnValueOnce: [Function (anonymous)],
mockResolvedValue: [Function (anonymous)],
mockResolvedValueOnce: [Function (anonymous)],
mockRejectedValue: [Function (anonymous)],
mockRejectedValueOnce: [Function (anonymous)]
}
vi.mock()について
vi.mock()
を使用することで指定されたパスの関数をモックすることができます.
第一引数にモックしたい関数のパスを指定します.
第二引数にモックしたい関数とモックが返す関数を設定することができます.
また, vi.mock()
はホイスティングされてしまうのでグローバル変数にアクセスすることができません.
使用例
// sum.ts
export const sum = (a: number, b: number) => a + b
// vitest.test.ts
import '@testing-library/jest-dom/vitest'
import { test, expect, vi } from 'vitest'
import { sum } from './sum'
vi.mock('./sum', () => ({
sum: () => 100,
}))
test('vi.mock', () => {
expect(sum(1, 2)).toBe(100)
})
エラーになるケース1
下記のコードの場合, vi.mock()
の中でグローバル変数であるreturnValue
にアクセスしているため, エラーが発生します.また, モックが返す値には変数ではなく, 関数を設定する必要があります.
// sum.ts
export const sum = (a: number, b: number) => a + b
// vitest.test.ts
const returnValue = 100
vi.mock('./sum', () => ({
sum: returnValue,
}))
test('vi.mock', () => {
expect(sum(1, 2)).toBe(100)
})
FAIL src/pages/blogs/vitest.test.tsx [ src/pages/blogs/vitest.test.tsx ]
Error: [vitest] There was an error when mocking a module. If you are using "vi.mock" factory, make sure there are no top level variables inside, since this call is hoisted to top of the file. Read more: https://vitest.dev/api/vi.html#vi-mock
❯ src/pages/blogs/vitest.test.tsx:6:25
4|
5| const returnValue = 3
6| vi.mock('./sum', () => ({
| ^
7| sum: returnValue,
8| }))
Caused by: ReferenceError: Cannot access 'returnValue' before initialization
解決策
下記のように, 関数を返すようにすると問題なく動作します.
vi.mock('./sum', () => ({
sum: () => returnValue,
}))
エラーになるケース2
この場合もやはり, vi.mock()
がホイスティングされるので, グローバル変数にはアクセスすることができません.
mockReturnValue(returnValue)
を使い,返す値をモックしているが, このときにグローバル変数であるreturnValue
にアクセスしているため, エラーが発生します.
// sum.ts
export const sum = (a: number, b: number) => a + b
// vitest.test.ts
const returnValue = 3
vi.mock('./sum', () => ({
sum: vi.fn().mockReturnValue(returnValue),
}))
test('vi.mock', () => {
expect(sum(1, 2)).toBe(3)
})
FAIL src/pages/blogs/vitest.test.tsx [ src/pages/blogs/vitest.test.tsx ]
Error: [vitest] There was an error when mocking a module. If you are using "vi.mock" factory, make sure there are no top level variables inside, since this call is hoisted to top of the file. Read more: https://vitest.dev/api/vi.html#vi-mock
❯ src/pages/blogs/vitest.test.tsx:6:25
4|
5| // vi.mock('./sum', () => ({
6| // sum: () => 3,
| ^
7| // }))
8|
Caused by: ReferenceError: Cannot access 'returnValue' before initialization
解決策
下記のようなコードにすることで, sum
が呼び出されるまで, returnValue
にアクセスすることを遅らせることができるため, 正しく動作します.
vi.mock('./sum', () => ({
sum: vi.fn(() => returnValue),
}))
テスト内でvi.mock()でモックされた関数が返す値を変更するには
テスト内でモック関数の戻り値を変更するには、以下のようにします。
この場合, vi.mock()
が実行されたときには, グローバル変数であるmockSum
にはアクセスできません.
しかし, sum
が呼び出されるのはテスト内であるため, テスト内でmockSum
の動作を設定すれば問題なく動作します.
// sum.ts
export const sum = (a: number, b: number) => a + b
// vitest.test.ts
let mockSum: (a: number, b: number) => number
vi.mock('./sum', () => ({
sum: (a: number, b: number) => mockSum(a, b),
}))
test('vi.mock', () => {
mockSum = vi.fn(() => 100) // sumが返す値を100に変更
sum(1, 2) // sumを呼び出す
expect(mockSum).toBeCalled() // mockSumが呼び出されたか確認
expect(sum(1, 2)).toBe(100) // sumが100を返すか確認
})
Discussion