Skip to content

模拟

TIP

刚接触模拟?先从 模拟函数 教程开始,了解 vi.fnvi.spyOnvi.mock 的实际操作。

在编写测试时,总会有需要为内部或外部服务创建“伪造”版本的时候。这通常被称为 模拟。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,
  },
})