Skip to content

模拟

在编写测试时,迟早需要创建一个内部或外部服务的“伪造”版本。这通常被称为 mocking(模拟)。Vitest 通过其 vi 辅助工具提供实用函数来帮助你。你可以从 vitest 导入它,如果启用了 global 配置,也可以全局访问它。

WARNING

务必记得在每次测试运行之前或之后清除或恢复 mock,以撤销运行之间的 mock 状态更改!请参阅 mockReset 文档了解更多信息。

如果你不熟悉 vi.fnvi.mockvi.spyOn 方法,请先查看 API 部分

Vitest 有一份关于模拟的综合指南列表:

若想更简单快速地开始模拟,你可以查看下面的速查表。

速查表

我想要…

模拟导出的变量

example.js
js
export const getter = 'variable'
example.test.ts
ts
import * as exports from './example.js'

vi.spyOn(exports, 'getter', 'get').mockReturnValue('mocked')

WARNING

这在浏览器模式(Browser Mode)下不起作用。有关解决方法,请参阅 限制

模拟导出的函数

  1. 使用 vi.mock 的示例:

WARNING

别忘了 vi.mock 调用会被提升到文件顶部。它总是在所有导入之前执行。

example.js
ts
export function method() {}
ts
import { method } from './example.js'

vi.mock('./example.js', () => ({
  method: vi.fn()
}))
  1. 使用 vi.spyOn 的示例:
ts
import * as exports from './example.js'

vi.spyOn(exports, 'method').mockImplementation(() => {})

WARNING

vi.spyOn 示例在浏览器模式(Browser Mode)下不起作用。有关解决方法,请参阅 限制

模拟导出的类实现

  1. 使用伪造 class 的示例:
example.js
ts
export class SomeClass {}
ts
import { SomeClass } from './example.js'

vi.mock(import('./example.js'), () => {
  const SomeClass = vi.fn(class FakeClass {
    someMethod = vi.fn()
  })
  return { SomeClass }
})
  1. 使用 vi.spyOn 的示例:
ts
import * as mod from './example.js'

vi.spyOn(mod, 'SomeClass').mockImplementation(class FakeClass {
  someMethod = vi.fn()
})

WARNING

vi.spyOn 示例在浏览器模式(Browser Mode)下不起作用。有关解决方法,请参阅 限制

监听函数返回的对象

  1. 使用缓存的示例:
example.js
ts
export function useObject() {
  return { method: () => true }
}
useObject.js
ts
import { useObject } from './example.js'

const obj = useObject()
obj.method()
useObject.test.js
ts
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()

模拟模块的一部分

ts
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 的时间。

ts
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

ts
vi.stubGlobal('__VERSION__', '1.0.0')
expect(__VERSION__).toBe('1.0.0')

模拟 import.meta.env

  1. 要更改环境变量,你可以直接为其赋予新值。

WARNING

环境变量值在不同测试之间 不会 自动重置。

ts
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')
})
  1. 如果你想自动重置值,可以使用 vi.stubEnv 辅助工具并启用 unstubEnvs 配置选项(或在 beforeEach 钩子中手动调用 vi.unstubAllEnvs):
ts
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')
})
vitest.config.ts
ts
export default defineConfig({
  test: {
    unstubEnvs: true,
  },
})