模拟对象
在编写测试时,迟早会需要创建一个内部或外部服务的 "fake" 版本。这通常被称为mocking。Vitest 通过其 vi
辅助工具提供了实用函数来帮助您。我们可以从 vitest
中导入它,或者如果启用了 global
配置,也可以全局访问它。
WARNING
不要忘记在每次测试运行前后清除或恢复模拟对象,以撤消运行测试时模拟对象状态的更改!有关更多信息,请参阅 mockReset
文档。
如果你不熟悉 vi.fn
、vi.mock
或 vi.spyOn
方法,请先查看API部分。
Vitest has a comprehensive list of guides regarding mocking:
- Mocking Classes
- Mocking Dates
- Mocking the File System
- Mocking Functions
- Mocking Globals
- Mocking Modules
- Mocking Requests
- Mocking Timers
为了更简单快捷地开始使用模拟,你可以查看下面的备忘单。
备忘单
I want to…
Mock exported variables
export const getter = 'variable'
import * as exports from './example.js'
vi.spyOn(exports, 'getter', 'get').mockReturnValue('mocked')
WARNING
此方法在浏览器模式中无法使用。如需替代方案,请查看 限制部分。
对模块中导出的函数进行 mock。
import * as exports from 'some-path'
vi.spyOn(exports, 'getter', 'get')
vi.spyOn(exports, 'setter', 'set')
模拟模块导出函数
vi.mock
的示例:
WARNING
不要忘记将 vi.mock
调用提升到文件顶部。它将始终在所有导入之前执行。
export function method() {}
import { method } from './example.js'
vi.mock('./example.js', () => ({
method: vi.fn()
}))
vi.spyOn
的示例:
import * as exports from './example.js'
vi.spyOn(exports, 'method').mockImplementation(() => {})
WARNING
vi.spyOn
示例在浏览器模式中无法使用。如需替代方案,请查看 限制部分。
vi.mock
和 .prototype
的示例:
- 一个使用假 class 的示例:
export class SomeClass {}
import { SomeClass } from './example.js'
vi.mock(import('./example.js'), () => {
const SomeClass = vi.fn(class FakeClass {
someMethod = vi.fn()
})
return { SomeClass }
})
- Example with
vi.spyOn
:
import * as mod from './example.js'
vi.spyOn(mod, 'SomeClass').mockImplementation(class FakeClass {
someMethod = vi.fn()
})
WARNING
vi.spyOn 的示例无法在浏览器模式中正常使用。如需替代方案,请查看 限制部分。
监听一个函数是否返回了一个对象
- 使用 cache 的示例:
export function useObject() {
return { method: () => true }
}
import { useObject } from './example.js'
const obj = useObject()
obj.method()
import { useObject } from './example.js'
vi.mock(import('./example.js'), () => {
let _cache
const useObject = () => {
if (!_cache) {
_cache = {
method: vi.fn(),
}
}
// 现在每次调用 useObject() 后,都会
// 返回相同的对象引用
return _cache
}
return { useObject }
})
const obj = useObject()
// obj.method 在 some-path 内调用
expect(obj.method).toHaveBeenCalled()
模拟部分 module
import { mocked, original } from './some-path.js'
vi.mock(import('./some-path.js'), async (importOriginal) => {
const mod = await importOriginal()
return {
...mod,
mocked: vi.fn(),
}
})
original() // 有原始的行为
mocked() // 是一个 spy 函数
WARNING
别忘了,这只是 mocks external access。在本例中,如果 original
在内部调用 mocked
,它将始终调用模块中定义的函数,而不是 mock 工厂中的函数。
模拟当前日期
要模拟 Date
的时间,你可以使用 vi.setSystemTime
辅助函数。 该值将不会在不同的测试之间自动重置。
请注意,使用 vi.useFakeTimers
也会更改 Date
的时间。
const mockDate = new Date(2022, 0, 1)
vi.setSystemTime(mockDate)
const now = new Date()
expect(now.valueOf()).toBe(mockDate.valueOf())
// 重置模拟的时间
vi.useRealTimers()
模拟全局变量
你可以通过为 globalThis
赋值或使用 vi.stubGlobal
助手来设置全局变量。 使用 vi.stubGlobal
时,不会在不同的测试之间自动重置,除非你启用 unstubGlobals
配置选项或调用 vi.unstubAllGlobals
。
vi.stubGlobal('__VERSION__', '1.0.0')
expect(__VERSION__).toBe('1.0.0')
模拟 import.meta.env
- 要更改环境变量,你只需为其分配一个新值即可。 该值将不会在不同的测试之间自动重置。
WARNING
环境变量值将在不同的测试之间不会自动重置。
import { beforeEach, expect, it } from 'vitest'
// 你可以在 beforeEach 钩子里手动重置
const originalViteEnv = import.meta.env.VITE_ENV
beforeEach(() => {
import.meta.env.VITE_ENV = originalViteEnv
})
it('changes value', () => {
import.meta.env.VITE_ENV = 'staging'
expect(import.meta.env.VITE_ENV).toBe('staging')
})
- 如果你想自动重置值,可以使用启用了
unstubEnvs
配置选项的vi.stubEnv
助手(或调用vi.unstubAllEnvs
在beforeEach
钩子中手动执行):
import { expect, it, vi } from 'vitest'
// 在运行测试之前, "VITE_ENV" 的值是 "test"
import.meta.env.VITE_ENV === 'test'
it('changes value', () => {
vi.stubEnv('VITE_ENV', 'staging')
expect(import.meta.env.VITE_ENV).toBe('staging')
})
it('the value is restored before running an other test', () => {
expect(import.meta.env.VITE_ENV).toBe('test')
})
export default defineConfig({
test: {
unstubEnvs: true,
},
})