模拟
在编写测试时,迟早需要创建一个内部或外部服务的“伪造”版本。这通常被称为 mocking(模拟)。Vitest 通过其 vi 辅助工具提供实用函数来帮助你。你可以从 vitest 导入它,如果启用了 global 配置,也可以全局访问它。
WARNING
务必记得在每次测试运行之前或之后清除或恢复 mock,以撤销运行之间的 mock 状态更改!请参阅 mockReset 文档了解更多信息。
如果你不熟悉 vi.fn、vi.mock 或 vi.spyOn 方法,请先查看 API 部分。
Vitest 有一份关于模拟的综合指南列表:
若想更简单快速地开始模拟,你可以查看下面的速查表。
速查表
我想要…
模拟导出的变量
export const getter = 'variable'import * as exports from './example.js'
vi.spyOn(exports, 'getter', 'get').mockReturnValue('mocked')WARNING
这在浏览器模式(Browser Mode)下不起作用。有关解决方法,请参阅 限制。
模拟导出的函数
- 使用
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 示例在浏览器模式(Browser Mode)下不起作用。有关解决方法,请参阅 限制。
模拟导出的类实现
- 使用伪造
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 }
})- 使用
vi.spyOn的示例:
import * as mod from './example.js'
vi.spyOn(mod, 'SomeClass').mockImplementation(class FakeClass {
someMethod = vi.fn()
})WARNING
vi.spyOn 示例在浏览器模式(Browser Mode)下不起作用。有关解决方法,请参阅 限制。
监听函数返回的对象
- 使用缓存的示例:
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()模拟模块的一部分
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
别忘了这仅 模拟 外部 访问。在此示例中,如果 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')
})- 如果你想自动重置值,可以使用
vi.stubEnv辅助工具并启用unstubEnvs配置选项(或在beforeEach钩子中手动调用vi.unstubAllEnvs):
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,
},
})