如何使用Jest测试输出是随机的函数?

Bog*_*gin 18 javascript jestjs

如何使用Jest测试输出是随机的函数?像这样:

import cuid from 'cuid';  
const functionToTest = (value) => ({
    [cuid()]: {
        a: Math.random(),
        b: new Date().toString(),
        c: value,
    }
});
Run Code Online (Sandbox Code Playgroud)

所以输出functionToTest('Some predictable value')将是这样的:

{
  'cixrchnp60000vhidc9qvd10p': {
    a: 0.08715126430943698,
    b: 'Tue Jan 10 2017 15:20:58 GMT+0200 (EET)',
    c: 'Some predictable value'
  },
}
Run Code Online (Sandbox Code Playgroud)

小智 59

我用了:

beforeEach(() => {
    jest.spyOn(global.Math, 'random').mockReturnValue(0.123456789);
});

afterEach(() => {
    jest.spyOn(global.Math, 'random').mockRestore();
})
Run Code Online (Sandbox Code Playgroud)

在测试之外添加和恢复功能很容易。

  • 这应该是公认的答案,因为它保留在模拟和间谍的测试词典中。这仍然会调用 `Math.random`,忽略返回值,但谁在乎呢? (2认同)

Stu*_*att 36

这是我放在测试文件顶部的内容:

const mockMath = Object.create(global.Math);
mockMath.random = () => 0.5;
global.Math = mockMath;
Run Code Online (Sandbox Code Playgroud)

在从该文件运行的测试中,Math.random始终返回0.5.

完全可靠的信用应该是这个想法:https://stackoverflow.com/a/40460395/2140998,它澄清了这种覆盖是特定于测试的.我Object.create只是额外的一点点谨慎,避免篡改Math自己的内部.

  • 只是`global.Math.random =()=> 0.5;`似乎也有效。 (2认同)
  • 测试用例后不需要恢复 global.Math 吗? (2认同)

Tim*_*Tim 6

我已经采用了Stuart Watt的解决方案并运行了它(并带走了一些)。Stuart的解决方案很好,但是让随机数生成器始终吐出0.5的想法让我不知所措-似乎在某些情况下,您会指望一些差异。我还想模拟crypto.randomBytes我的密码盐(使用Jest服务器端)。我花了一些时间在此上,所以我想我会分享知识。

我注意到的一件事是,即使您有可重复的数字流,引入一个新的呼叫也Math.random()可能会打乱所有后续呼叫。我找到了解决此问题的方法。这种方法几乎适用于您需要模拟的任何随机事物。

(边注:如果你要偷这个,你需要安装Chance.js - yarn/npm add/install chance

嘲笑Math.random,把这个在你的某个文件指出package.json{"jest":{"setupFiles"}数组:

const Chance = require('chance')

const chances = {}

const mockMath = Object.create(Math)
mockMath.random = (seed = 42) => {
  chances[seed] = chances[seed] || new Chance(seed)
  const chance = chances[seed]
  return chance.random()
}

global.Math = mockMath
Run Code Online (Sandbox Code Playgroud)

您会注意到,Math.random()现在有一个参数-种子。该种子可以是字符串。这意味着在编写代码时,可以按名称调用所需的随机数生成器。当我在代码中添加测试以检查其是否有效时,我没有为它添加种子。它搞砸了我以前嘲笑的Math.random()快照。但是,当我将其更改Math.random('mathTest')为时,它创建了一个名为“ mathTest”的新生成器,并停止截取默认序列中的序列。

我还嘲笑crypto.randomBytes了我的密码盐。因此,当我编写代码生成盐时,我可能会写crypto.randomBytes(32, 'user sign up salt').toString('base64')。这样,我可以确定没有后续调用crypto.randomBytes会弄乱我的序列。

如果其他人有兴趣以crypto这种方式进行模拟,请按以下步骤进行。将此代码放入<rootDir>/__mocks__/crypto.js

const crypto = require.requireActual('crypto')
const Chance = require('chance')

const chances = {}

const mockCrypto = Object.create(crypto)
mockCrypto.randomBytes = (size, seed = 42, callback) => {
  if (typeof seed === 'function') {
    callback = seed
    seed = 42
  }

  chances[seed] = chances[seed] || new Chance(seed)
  const chance = chances[seed]

  const randomByteArray = chance.n(chance.natural, size, { max: 255 })
  const buffer = Buffer.from(randomByteArray)

  if (typeof callback === 'function') {
    callback(null, buffer)
  }
  return buffer
}

module.exports = mockCrypto
Run Code Online (Sandbox Code Playgroud)

然后调用jest.mock('crypto')(同样,我将其保存在我的“ setupFiles”之一中)。自发布以来,我继续使其与回调方法兼容(尽管我无意以这种方式使用它)。

这两段代码通过了所有这17个测试(我__clearChances__beforeEach()s 创建了功能-它只是从chances哈希中删除了所有键)

更新:现在已经使用了几天,我认为它运行良好。我唯一认为的是,也许更好的策略可能是创建一个Math.useSeed在需要测试的顶部运行的函数Math.random


cri*_*nge 5

我会问自己以下问题:

  • 我真的需要测试随机输出吗?如果必须,我很可能会测试范围或确保我收到一个有效格式的数字,而不是值本身
  • 是否足以测试 的值c

有时有一种方法可以将随机值的生成封装在Mock 中并覆盖测试中的生成以仅返回已知值。这是我的代码中的常见做法。如何模拟像 new Date()这样的构造函数jestjs 中听起来是类似的方法。