基础函数模拟
使用mock 函数创建模拟。
Jest 兼容性
或者,你也可以像在 Jest 中那样使用jest.fn() 函数。它的行为完全相同。
模拟函数属性
mock() 的返回值是一个带有附加属性的新函数。
可用属性和方法
模拟函数实现了以下属性和方法:| 属性/方法 | 描述 |
|---|---|
mockFn.getMockName() | 返回模拟名称 |
mockFn.mock.calls | 每次调用的参数数组 |
mockFn.mock.results | 每次调用的返回值数组 |
mockFn.mock.instances | 每次调用的 this 上下文数组 |
mockFn.mock.contexts | 每次调用的 this 上下文数组 |
mockFn.mock.lastCall | 最近一次调用的参数 |
mockFn.mockClear() | 清空调用历史 |
mockFn.mockReset() | 清空调用历史且移除实现 |
mockFn.mockRestore() | 恢复原始实现 |
mockFn.mockImplementation(fn) | 设置新的实现 |
mockFn.mockImplementationOnce(fn) | 只为下一次调用设置实现 |
mockFn.mockName(name) | 设置模拟名称 |
mockFn.mockReturnThis() | 设置返回值为 this |
mockFn.mockReturnValue(value) | 设置返回值 |
mockFn.mockReturnValueOnce(value) | 只为下一次调用设置返回值 |
mockFn.mockResolvedValue(value) | 设置解析(resolved)的 Promise 值 |
mockFn.mockResolvedValueOnce(value) | 只为下一次调用设置解析的 Promise 值 |
mockFn.mockRejectedValue(value) | 设置拒绝(rejected)的 Promise 值 |
mockFn.mockRejectedValueOnce(value) | 只为下一次调用设置拒绝的 Promise 值 |
mockFn.withImplementation(fn, callback) | 临时更改实现 |
实际示例
基础模拟用法
动态模拟实现
异步模拟
使用 spyOn() 创建间谍
可以不替换函数实现而追踪其调用。使用spyOn() 创建间谍;这些间谍可以用于 .toHaveBeenCalled() 和 .toHaveBeenCalledTimes()。
高级间谍用法
使用 mock.module() 进行模块模拟
模块模拟允许覆盖模块行为。使用mock.module(path: string, callback: () => Object) 来模拟模块。
import 和 require。
重写已导入模块
如果你需要重写已经导入的模块,无需额外操作。调用mock.module() 即可覆盖模块。
提前声明与预加载
如果你需要确保模块在导入前被模拟,应使用--preload 在测试运行前加载模拟。
terminal
bunfig.toml:
bunfig.toml
模块模拟最佳实践
何时使用预加载
如果模拟的模块已被导入,会发生什么? 如果模拟了已导入的模块,模块缓存中会更新该模块。这意味着导入它的模块将得到模拟版本,但原始模块已经执行过了,相关副作用已发生。 如果你想阻止原模块执行,应使用--preload 在测试前加载模拟。
实际模块模拟示例
模拟外部依赖
全局模拟函数
清除所有模拟
重置所有模拟函数状态(调用、结果等),但不还原它们的原始实现:.mock.calls、.mock.instances、.mock.contexts 和 .mock.results,但不像 mock.restore() 那样恢复原始实现。
恢复所有模拟
与逐个调用mockFn.mockRestore() 不同,可用 mock.restore() 一次性恢复所有模拟。此操作不会重置通过 mock.module() 覆盖的模块值。
mock.restore() 放入每个测试文件的 afterEach 钩子或测试预加载代码中,可以减少测试代码量。
Vitest 兼容性
为方便移植 Vitest 编写的测试,Bun 提供了vi 对象作为 Jest 模拟 API 部分的别名:
实现细节
了解mock.module() 的工作原理可帮助更有效地使用:
缓存交互
模块模拟会与 ESM 和 CommonJS 模块缓存交互。惰性求值
模拟工厂回调只有在模块真正被导入或 require 时才会被执行。路径解析
Bun 会自动解析模块标识符,支持:- 相对路径(
'./module') - 绝对路径(
'/path/to/module') - 包名(
'lodash')
导入时机影响
- 在首次导入前模拟:原模块不会执行副作用
- 在导入后模拟:原模块副作用已经发生
--preload 来防止副作用。
实时绑定
模拟的 ESM 模块保持实时绑定,因此修改模拟会更新所有已有导入。高级模式
工厂函数
条件式模拟
模拟清理模式
最佳实践
保持模拟简单
使用类型安全的模拟
测试模拟行为
备注
自动模拟
目前不支持__mocks__ 目录和自动模拟。如此阻碍你迁移到 Bun,请提交 issue。