Vitest Mock 功能 #
Mock 概述 #
Mock(模拟)是单元测试中的核心概念,用于隔离测试单元,模拟外部依赖的行为。Vitest 提供了强大的 Mock 功能,通过 vi 对象提供各种 Mock 方法。
为什么需要 Mock? #
text
┌─────────────────────────────────────────────────────────────┐
│ 测试隔离的重要性 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 没有 Mock: │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 测试代码 │ -> │ 真实API │ -> │ 数据库 │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │ │ │ │
│ │ 可能失败 可能失败 │
│ │ 网络问题 连接问题 │
│ │ 速度慢 数据污染 │
│ │
│ 使用 Mock: │
│ ┌─────────┐ ┌─────────┐ │
│ │ 测试代码 │ -> │ Mock API │ │
│ └─────────┘ └─────────┘ │
│ │ │ │
│ │ 可控、稳定 │
│ │ 快速、隔离 │
│ │
└─────────────────────────────────────────────────────────────┘
vi 对象 #
vi 是 Vitest 的 Mock 工具对象:
typescript
import { vi } from 'vitest'
// vi 提供的方法
vi.fn() // 创建 Mock 函数
vi.spyOn() // 创建间谍函数
vi.mock() // Mock 模块
vi.unmock() // 取消 Mock
vi.doMock() // 条件 Mock
vi.importActual() // 导入真实模块
vi.importMock() // 导入 Mock 模块
vi.useFakeTimers() // 使用假定时器
vi.useRealTimers() // 使用真实定时器
vi.setSystemTime() // 设置系统时间
vi.mocked() // 类型断言
函数 Mock #
vi.fn() 基本用法 #
创建一个 Mock 函数:
typescript
import { vi, expect, test } from 'vitest'
test('vi.fn() basic', () => {
// 创建 Mock 函数
const mockFn = vi.fn()
// 调用函数
mockFn('hello')
mockFn('world')
// 验证调用
expect(mockFn).toHaveBeenCalled()
expect(mockFn).toHaveBeenCalledTimes(2)
expect(mockFn).toHaveBeenCalledWith('hello')
expect(mockFn).toHaveBeenLastCalledWith('world')
})
设置返回值 #
typescript
import { vi, expect, test } from 'vitest'
test('mock return values', () => {
// 固定返回值
const mockFn1 = vi.fn().mockReturnValue(42)
expect(mockFn1()).toBe(42)
// 一次性返回值
const mockFn2 = vi.fn()
.mockReturnValueOnce(1)
.mockReturnValueOnce(2)
.mockReturnValue(3)
expect(mockFn2()).toBe(1)
expect(mockFn2()).toBe(2)
expect(mockFn2()).toBe(3)
expect(mockFn2()).toBe(3)
// 返回 this
const obj = { a: 1 }
const mockFn3 = vi.fn().mockReturnThis()
expect(mockFn3.call(obj)).toBe(obj)
})
设置实现 #
typescript
import { vi, expect, test } from 'vitest'
test('mock implementation', () => {
// 自定义实现
const mockFn1 = vi.fn((x: number) => x * 2)
expect(mockFn1(5)).toBe(10)
// 一次性实现
const mockFn2 = vi.fn()
.mockImplementationOnce((x: number) => x * 2)
.mockImplementationOnce((x: number) => x * 3)
.mockImplementation((x: number) => x * 4)
expect(mockFn2(5)).toBe(10)
expect(mockFn2(5)).toBe(15)
expect(mockFn2(5)).toBe(20)
expect(mockFn2(5)).toBe(20)
})
异步 Mock #
typescript
import { vi, expect, test } from 'vitest'
test('async mock', async () => {
// 返回 Promise
const mockFn1 = vi.fn().mockResolvedValue(42)
expect(await mockFn1()).toBe(42)
// 一次性 Promise
const mockFn2 = vi.fn()
.mockResolvedValueOnce(1)
.mockResolvedValueOnce(2)
.mockResolvedValue(3)
expect(await mockFn2()).toBe(1)
expect(await mockFn2()).toBe(2)
expect(await mockFn2()).toBe(3)
// 自定义异步实现
const mockFn3 = vi.fn().mockImplementation(async (id: number) => {
return { id, name: `User ${id}` }
})
const user = await mockFn3(1)
expect(user).toEqual({ id: 1, name: 'User 1' })
})
Mock 函数属性 #
typescript
import { vi, expect, test } from 'vitest'
test('mock function properties', () => {
const mockFn = vi.fn((x: number) => x * 2)
mockFn(1)
mockFn(2, 3)
mockFn('hello')
// 调用记录
console.log(mockFn.mock.calls)
// [[1], [2, 3], ['hello']]
// 返回值记录
console.log(mockFn.mock.results)
// [{ type: 'return', value: 2 }, { type: 'return', value: 4 }, ...]
// this 上下文
console.log(mockFn.mock.contexts)
// 调用实例
console.log(mockFn.mock.instances)
})
间谍函数 #
vi.spyOn() 基本用法 #
监视对象方法的调用:
typescript
import { vi, expect, test, beforeEach, afterEach } from 'vitest'
const obj = {
getName: () => 'John',
getAge: () => 30,
}
test('vi.spyOn() basic', () => {
// 创建间谍
const spy = vi.spyOn(obj, 'getName')
// 调用方法
const name = obj.getName()
// 验证调用
expect(spy).toHaveBeenCalled()
expect(spy).toHaveReturnedWith('John')
expect(name).toBe('John')
})
修改实现 #
typescript
import { vi, expect, test } from 'vitest'
test('spy implementation', () => {
const obj = {
getValue: () => 'original',
}
// 保留原始实现
const spy1 = vi.spyOn(obj, 'getValue')
expect(obj.getValue()).toBe('original')
// 替换实现
const spy2 = vi.spyOn(obj, 'getValue').mockReturnValue('mocked')
expect(obj.getValue()).toBe('mocked')
// 恢复原始实现
spy2.mockRestore()
expect(obj.getValue()).toBe('original')
})
间谍函数操作 #
typescript
import { vi, expect, test, beforeEach, afterEach } from 'vitest'
describe('spy operations', () => {
const obj = {
method: () => 'original',
}
let spy: ReturnType<typeof vi.spyOn>
beforeEach(() => {
spy = vi.spyOn(obj, 'method')
})
afterEach(() => {
spy.mockRestore() // 恢复原始实现
// 或
spy.mockReset() // 重置调用记录
// 或
spy.mockClear() // 清除调用记录
})
test('test 1', () => {
obj.method()
expect(spy).toHaveBeenCalled()
})
test('test 2', () => {
obj.method()
expect(spy).toHaveBeenCalled()
})
})
spy 与 fn 的区别 #
typescript
import { vi, expect, test } from 'vitest'
test('spy vs fn', () => {
const obj = {
method: () => 'original',
}
// vi.fn() 创建新函数
const mockFn = vi.fn()
mockFn()
expect(mockFn).toHaveBeenCalled()
// vi.spyOn() 监视现有方法
const spy = vi.spyOn(obj, 'method')
obj.method() // 调用原始方法
expect(spy).toHaveBeenCalled()
expect(obj.method()).toBe('original') // 原始实现仍然工作
})
模块 Mock #
vi.mock() 基本用法 #
Mock 整个模块:
typescript
// api.ts
export async function fetchUser(id: number) {
const response = await fetch(`/api/users/${id}`)
return response.json()
}
// api.test.ts
import { vi, expect, test } from 'vitest'
// Mock 模块
vi.mock('./api', () => ({
fetchUser: vi.fn(async (id: number) => ({
id,
name: `Mock User ${id}`,
})),
}))
import { fetchUser } from './api'
test('mocked module', async () => {
const user = await fetchUser(1)
expect(user).toEqual({ id: 1, name: 'Mock User 1' })
})
自动 Mock #
typescript
import { vi, expect, test } from 'vitest'
// 自动 Mock,所有导出变为 vi.fn()
vi.mock('./api')
import { fetchUser, deleteUser } from './api'
test('auto mock', async () => {
// 设置返回值
vi.mocked(fetchUser).mockResolvedValue({ id: 1, name: 'Mock' })
const user = await fetchUser(1)
expect(user).toEqual({ id: 1, name: 'Mock' })
// 验证调用
expect(fetchUser).toHaveBeenCalledWith(1)
})
部分 Mock #
typescript
import { vi, expect, test } from 'vitest'
// 部分模块保持原始实现
vi.mock('./utils', async (importOriginal) => {
const original = await importOriginal<typeof import('./utils')>()
return {
...original, // 保持其他导出
// 只 Mock 特定函数
formatDate: vi.fn((date: Date) => 'mocked date'),
}
})
import { formatDate, calculateSum } from './utils'
test('partial mock', () => {
expect(formatDate(new Date())).toBe('mocked date')
expect(calculateSum(1, 2)).toBe(3) // 原始实现
})
vi.importActual() #
导入真实模块:
typescript
import { vi, expect, test } from 'vitest'
vi.mock('./utils', async () => {
const actual = await vi.importActual<typeof import('./utils')>('./utils')
return {
...actual,
// 只 Mock 特定函数
fetchData: vi.fn().mockResolvedValue({ data: 'mocked' }),
}
})
vi.importMock() #
导入 Mock 模块:
typescript
import { vi, expect, test } from 'vitest'
test('import mock', async () => {
const mockedModule = await vi.importMock<typeof import('./api')>('./api')
// mockedModule 所有导出都是 vi.fn()
mockedModule.fetchUser.mockResolvedValue({ id: 1 })
})
vi.doMock() #
条件 Mock:
typescript
import { vi, expect, test, beforeEach } from 'vitest'
beforeEach(() => {
vi.doMock('./config', () => ({
API_URL: 'http://test-api.com',
}))
})
test('conditional mock', async () => {
// 在测试内部导入
const { API_URL } = await import('./config')
expect(API_URL).toBe('http://test-api.com')
})
vi.unmock() #
取消 Mock:
typescript
import { vi, expect, test } from 'vitest'
vi.mock('./api')
test('mocked', async () => {
const { fetchUser } = await import('./api')
// fetchUser 是 Mock 函数
})
vi.unmock('./api')
test('unmocked', async () => {
const { fetchUser } = await import('./api')
// fetchUser 是真实函数
})
定时器 Mock #
vi.useFakeTimers() #
使用假定时器:
typescript
import { vi, expect, test, beforeEach, afterEach } from 'vitest'
beforeEach(() => {
vi.useFakeTimers()
})
afterEach(() => {
vi.useRealTimers()
})
test('setTimeout', () => {
const callback = vi.fn()
setTimeout(callback, 1000)
// 时间未到,未调用
expect(callback).not.toHaveBeenCalled()
// 快进时间
vi.advanceTimersByTime(1000)
// 已调用
expect(callback).toHaveBeenCalled()
})
test('setInterval', () => {
const callback = vi.fn()
setInterval(callback, 1000)
// 快进 3 秒
vi.advanceTimersByTime(3000)
// 调用 3 次
expect(callback).toHaveBeenCalledTimes(3)
})
控制时间 #
typescript
import { vi, expect, test, beforeEach, afterEach } from 'vitest'
beforeEach(() => {
vi.useFakeTimers()
})
afterEach(() => {
vi.useRealTimers()
})
test('time control', () => {
const callback = vi.fn()
setTimeout(callback, 1000)
setTimeout(callback, 2000)
setTimeout(callback, 3000)
// 快进到下一个定时器
vi.advanceTimersToNextTimer()
expect(callback).toHaveBeenCalledTimes(1)
// 快进到下一个定时器
vi.advanceTimersToNextTimer()
expect(callback).toHaveBeenCalledTimes(2)
// 快进到下一个定时器
vi.advanceTimersToNextTimer()
expect(callback).toHaveBeenCalledTimes(3)
})
test('run all timers', () => {
const callback = vi.fn()
setTimeout(callback, 1000)
setTimeout(callback, 2000)
setTimeout(callback, 3000)
// 运行所有定时器
vi.runAllTimers()
expect(callback).toHaveBeenCalledTimes(3)
})
test('run only pending timers', () => {
const callback = vi.fn()
setTimeout(callback, 1000)
// 只运行待处理的定时器
vi.runOnlyPendingTimers()
expect(callback).toHaveBeenCalledTimes(1)
})
设置系统时间 #
typescript
import { vi, expect, test, beforeEach, afterEach } from 'vitest'
beforeEach(() => {
vi.useFakeTimers()
})
afterEach(() => {
vi.useRealTimers()
})
test('set system time', () => {
// 设置系统时间
const now = new Date('2024-01-15T10:00:00')
vi.setSystemTime(now)
// Date.now() 返回设置的时间
expect(Date.now()).toBe(now.getTime())
// new Date() 返回设置的时间
expect(new Date()).toEqual(now)
})
定时器 Mock 配置 #
typescript
import { vi, expect, test, beforeEach, afterEach } from 'vitest'
beforeEach(() => {
vi.useFakeTimers({
// 只 Mock 特定定时器
toFake: ['setTimeout', 'setInterval'],
// 设置初始时间
now: new Date('2024-01-01'),
// 循环上限
loopLimit: 1000,
})
})
afterEach(() => {
vi.useRealTimers()
})
全局 Mock #
Mock 全局变量 #
typescript
import { vi, expect, test, beforeEach, afterEach } from 'vitest'
describe('global mock', () => {
const originalFetch = global.fetch
beforeEach(() => {
global.fetch = vi.fn()
})
afterEach(() => {
global.fetch = originalFetch
})
test('mock fetch', async () => {
vi.mocked(fetch).mockResolvedValue({
ok: true,
json: async () => ({ data: 'mocked' }),
} as Response)
const response = await fetch('/api/data')
const data = await response.json()
expect(data).toEqual({ data: 'mocked' })
})
})
Mock localStorage #
typescript
import { vi, expect, test, beforeEach, afterEach } from 'vitest'
describe('localStorage mock', () => {
const localStorageMock = (() => {
let store: Record<string, string> = {}
return {
getItem: (key: string) => store[key] || null,
setItem: (key: string, value: string) => {
store[key] = value
},
removeItem: (key: string) => {
delete store[key]
},
clear: () => {
store = {}
},
}
})()
beforeEach(() => {
Object.defineProperty(global, 'localStorage', {
value: localStorageMock,
})
})
test('localStorage', () => {
localStorage.setItem('key', 'value')
expect(localStorage.getItem('key')).toBe('value')
})
})
Mock console #
typescript
import { vi, expect, test, beforeEach, afterEach } from 'vitest'
describe('console mock', () => {
let consoleSpy: ReturnType<typeof vi.spyOn>
beforeEach(() => {
consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
})
afterEach(() => {
consoleSpy.mockRestore()
})
test('console.log', () => {
console.log('test message')
expect(consoleSpy).toHaveBeenCalledWith('test message')
})
})
Mock 最佳实践 #
1. 在 beforeEach 中设置 Mock #
typescript
import { vi, expect, test, beforeEach, afterEach } from 'vitest'
import { fetchUser } from './api'
vi.mock('./api')
describe('User tests', () => {
beforeEach(() => {
vi.clearAllMocks()
vi.mocked(fetchUser).mockResolvedValue({ id: 1, name: 'Test' })
})
afterEach(() => {
vi.restoreAllMocks()
})
test('test 1', async () => {
await fetchUser(1)
expect(fetchUser).toHaveBeenCalledWith(1)
})
test('test 2', async () => {
await fetchUser(2)
expect(fetchUser).toHaveBeenCalledWith(2)
})
})
2. 使用 vi.mocked() 类型断言 #
typescript
import { vi, expect, test } from 'vitest'
import { fetchUser } from './api'
vi.mock('./api')
test('typed mock', async () => {
// 类型安全的 Mock
vi.mocked(fetchUser).mockResolvedValue({ id: 1, name: 'Test' })
const user = await fetchUser(1)
expect(user.name).toBe('Test')
})
3. 隔离测试 #
typescript
import { vi, expect, test, beforeEach, afterEach } from 'vitest'
describe('isolated tests', () => {
beforeEach(() => {
vi.resetModules() // 重置模块缓存
})
afterEach(() => {
vi.restoreAllMocks()
})
test('test 1', async () => {
vi.doMock('./config', () => ({ value: 1 }))
const { value } = await import('./config')
expect(value).toBe(1)
})
test('test 2', async () => {
vi.doMock('./config', () => ({ value: 2 }))
const { value } = await import('./config')
expect(value).toBe(2)
})
})
4. Mock 清理 #
typescript
import { vi, expect, test, beforeEach, afterEach } from 'vitest'
describe('mock cleanup', () => {
beforeEach(() => {
vi.clearAllMocks() // 清除调用记录
// 或
vi.resetAllMocks() // 重置 Mock 实现
// 或
vi.restoreAllMocks() // 恢复原始实现
})
test('clean test', () => {
const mockFn = vi.fn()
mockFn()
expect(mockFn).toHaveBeenCalled()
})
})
Mock 速查表 #
| 方法 | 说明 |
|---|---|
vi.fn(impl?) |
创建 Mock 函数 |
vi.spyOn(obj, method) |
创建间谍函数 |
vi.mock(path, factory?) |
Mock 模块 |
vi.unmock(path) |
取消 Mock |
vi.doMock(path, factory?) |
条件 Mock |
vi.importActual(path) |
导入真实模块 |
vi.importMock(path) |
导入 Mock 模块 |
vi.useFakeTimers() |
使用假定时器 |
vi.useRealTimers() |
使用真实定时器 |
vi.setSystemTime(time) |
设置系统时间 |
vi.mocked(fn) |
类型断言 |
vi.clearAllMocks() |
清除所有调用记录 |
vi.resetAllMocks() |
重置所有 Mock |
vi.restoreAllMocks() |
恢复所有原始实现 |
vi.resetModules() |
重置模块缓存 |
下一步 #
现在你已经掌握了 Mock 功能,接下来学习 快照测试 了解如何使用快照测试 UI 组件!
最后更新:2026-03-28