如何在测试中Mock随机数生成器_确保随机逻辑的可预测性

4次阅读

math.random 在 jest 中直接 mock 失效,因其为只读属性,需用 Object.defineProperty 配合 writable: true 劫持,并在 beforeeach 设置、aftereach 恢复;推荐封装 getrandom 函数便于注入和测试。

如何在测试中Mock随机数生成器_确保随机逻辑的可预测性

为什么直接 mock Math.random 在 Jest 里会失效

因为 Math.random 是只读属性,Jest 的 jest.mockjest.spyOn 默认无法覆盖它——直接赋值会静默失败(非严格模式下)或报 TypeError: Cannot assign to read only property。很多测试跑着跑着“看起来通过”,其实是没真正 mock 成功,随机逻辑还在偷偷运行。

  • 必须用 Object.defineProperty 配合 writable: true 才能临时劫持
  • mock 要放在 beforeEach 里,且每次测试后需在 afterEach 恢复原生行为,否则污染后续测试
  • 注意 Node.js 环境下 global.Math 和浏览器环境一致,但某些测试 runner(如 Vitest)可能有额外沙箱机制,需确认是否启用 globals: true

Jest 中稳定替换 Math.random 的三步写法

不依赖第三方库,纯原生 JS 就能搞定。核心是绕过只读限制 + 精确控制返回值序列。

  • beforeEach 中用 Object.defineProperty(global.Math, 'random', { value: jest.fn(), writable: true })
  • 给 mock 函数设定返回值:比如 Math.random.mockReturnValueOnce(0.123).mockReturnValueOnce(0.456)
  • 务必在 afterEach 中调用 Math.random.mockRestore(),否则下次测试可能拿到上一轮残留的 mock 实例

示例:

beforeEach(() => {   Object.defineProperty(global.Math, 'random', {     value: jest.fn(),     writable: true,   }); });  it('uses fixed random sequence', () => {   Math.random.mockReturnValueOnce(0.2).mockReturnValueOnce(0.8);   expect(myRandomFunction()).toBe('high'); });

用封装函数替代直接调用 Math.random 更易测试

如果业务代码里散落着几十个 Math.random(),逐个 patch 不现实。更可持续的做法是:把随机能力抽成可注入的依赖。

  • 定义一个默认导出函数,比如 export const getRandom = () => Math.random()
  • 业务代码里统一调用 getRandom(),而不是裸写 Math.random()
  • 测试时用 jest.mock('./random', () => ({ getRandom: jest.fn().mockReturnValue(0.7) }))
  • 这样既避免污染全局,又让随机逻辑可被 DI 替换(比如未来换成真随机服务)

Node.js 里用 crypto.randomInt 怎么 mock

crypto.randomInt 是可写的普通函数,mock 比 Math.random 简单,但容易忽略它的参数行为和错误路径。

  • 直接 jest.mock('crypto') 后,require('crypto').randomInt 可被完全替换
  • 注意它支持两种签名:randomInt(max)randomInt(min, max),mock 时要按实际调用方式返回对应范围值
  • 若原逻辑有 try/catch 处理 RangeError,测试中应主动触发异常:比如 randomInt.mockImplementation(() => { throw new RangeError('invalid range'); })

示例:

jest.mock('crypto', () => ({   randomInt: jest.fn().mockReturnValue(42), }));

测试中最容易被忽略的不是“怎么 mock”,而是“什么时候恢复”——漏掉 mockRestore 或忘记重置模块缓存,会导致看似无关的测试突然失败,而且错误根本不提随机数的事。

text=ZqhQzanResources