如何使用 Jest 测试输出随机的函数?
How to test a function that output is random using Jest?
如何使用 Jest 测试输出随机的函数?像这样:
import cuid from 'cuid';
const functionToTest = (value) => ({
[cuid()]: {
a: Math.random(),
b: new Date().toString(),
c: value,
}
});
因此 functionToTest('Some predictable value')
的输出将类似于:
{
'cixrchnp60000vhidc9qvd10p': {
a: 0.08715126430943698,
b: 'Tue Jan 10 2017 15:20:58 GMT+0200 (EET)',
c: 'Some predictable value'
},
}
我会问自己以下问题:
- 我真的需要测试随机输出吗?如果必须,我很可能会测试范围或确保我收到的数字格式有效,而不是值本身
- 是否足以测试
c
的值?
有时有一种方法可以将随机值的生成封装在 Mock 中,并将测试中的生成覆盖为 return 只有已知值。这是我的代码中的常见做法。 听起来像 jestjs.
中的类似方法
这是我放在测试文件顶部的内容:
const mockMath = Object.create(global.Math);
mockMath.random = () => 0.5;
global.Math = mockMath;
在该文件的测试 运行 中,Math.random
总是 returns 0.5
。
这个想法应该得到充分的信任:,它阐明了这个覆盖是特定于测试的。我的 Object.create
只是我额外的一点额外警告,避免篡改 Math
本身的内部结构。
我已经采用了 Stuart Watt 的解决方案并 运行 使用了它(有点忘乎所以)。 Stuart 的解决方案很好,但让 运行dom 数字生成器始终吐出 0.5 的想法让我不知所措 - 似乎在某些情况下你会指望一些差异。我还想为我的密码盐模拟 crypto.randomBytes
(使用 Jest 服务器端)。我在这上面花了一些时间,所以我想我会分享这些知识。
我注意到的一件事是,即使您有可重复的数字流,引入对 Math.random()
的新调用也可能会搞砸所有后续调用。我找到了解决这个问题的方法。这种方法应该适用于几乎任何您需要模拟的 运行dom 事物。
(旁注:如果你想偷这个,你需要安装 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
您会注意到 Math.random()
现在有一个参数 - 种子。这个种子可以是一个字符串。这意味着,在编写代码时,您可以通过名称调用所需的 运行dom 号码生成器。当我向代码添加测试以检查这是否有效时,我没有放种子。它搞砸了我之前模拟的 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
然后只需调用 jest.mock('crypto')
(同样,我将它放在我的 "setupFiles" 之一中)。自从我发布它以来,我继续努力使其与回调方法兼容(尽管我无意以这种方式使用它)。
这两段代码通过了 these tests 的所有 17 个(我为 beforeEach()
创建了 __clearChances__
函数——它只是删除了 chances
中的所有键哈希)
更新:我已经用了几天了,我觉得效果很好。唯一的事情是我认为也许更好的策略是在需要 Math.random
的测试顶部创建 运行 的 Math.useSeed
函数
您可以随时使用 jest-mock-random
但它比第一个答案中提出的模拟它提供了更多的功能。
例如,您可以在 testmockRandomWith(0.6);
之前使用,而您在测试中的 Math.random 将始终 return 这个可预测值
模拟文字随机数据并不是真正的测试方式。如前所述,问题有点宽泛,因为 "how to test a function that output is random" 要求您对输出进行统计分析以确保有效的随机性 - 这可能是由伪随机数生成器的创建者完成的。
推断而不是 "that output is random" 意味着你想确保函数正常运行而不管随机数据然后只是模拟 Math.random 调用 return 符合你特定标准的数字(涵盖任何差异)就足够了。该功能是第三方边界,虽然需要测试,但不是根据我的推断正在测试的内容。除非是——在这种情况下,请参阅上一段。
我用过:
beforeEach(() => {
jest.spyOn(global.Math, 'random').mockReturnValue(0.123456789);
});
afterEach(() => {
jest.spyOn(global.Math, 'random').mockRestore();
})
在测试之外添加和恢复功能很容易。
如何使用 Jest 测试输出随机的函数?像这样:
import cuid from 'cuid';
const functionToTest = (value) => ({
[cuid()]: {
a: Math.random(),
b: new Date().toString(),
c: value,
}
});
因此 functionToTest('Some predictable value')
的输出将类似于:
{
'cixrchnp60000vhidc9qvd10p': {
a: 0.08715126430943698,
b: 'Tue Jan 10 2017 15:20:58 GMT+0200 (EET)',
c: 'Some predictable value'
},
}
我会问自己以下问题:
- 我真的需要测试随机输出吗?如果必须,我很可能会测试范围或确保我收到的数字格式有效,而不是值本身
- 是否足以测试
c
的值?
有时有一种方法可以将随机值的生成封装在 Mock 中,并将测试中的生成覆盖为 return 只有已知值。这是我的代码中的常见做法。
这是我放在测试文件顶部的内容:
const mockMath = Object.create(global.Math);
mockMath.random = () => 0.5;
global.Math = mockMath;
在该文件的测试 运行 中,Math.random
总是 returns 0.5
。
这个想法应该得到充分的信任:Object.create
只是我额外的一点额外警告,避免篡改 Math
本身的内部结构。
我已经采用了 Stuart Watt 的解决方案并 运行 使用了它(有点忘乎所以)。 Stuart 的解决方案很好,但让 运行dom 数字生成器始终吐出 0.5 的想法让我不知所措 - 似乎在某些情况下你会指望一些差异。我还想为我的密码盐模拟 crypto.randomBytes
(使用 Jest 服务器端)。我在这上面花了一些时间,所以我想我会分享这些知识。
我注意到的一件事是,即使您有可重复的数字流,引入对 Math.random()
的新调用也可能会搞砸所有后续调用。我找到了解决这个问题的方法。这种方法应该适用于几乎任何您需要模拟的 运行dom 事物。
(旁注:如果你想偷这个,你需要安装 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
您会注意到 Math.random()
现在有一个参数 - 种子。这个种子可以是一个字符串。这意味着,在编写代码时,您可以通过名称调用所需的 运行dom 号码生成器。当我向代码添加测试以检查这是否有效时,我没有放种子。它搞砸了我之前模拟的 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
然后只需调用 jest.mock('crypto')
(同样,我将它放在我的 "setupFiles" 之一中)。自从我发布它以来,我继续努力使其与回调方法兼容(尽管我无意以这种方式使用它)。
这两段代码通过了 these tests 的所有 17 个(我为 beforeEach()
创建了 __clearChances__
函数——它只是删除了 chances
中的所有键哈希)
更新:我已经用了几天了,我觉得效果很好。唯一的事情是我认为也许更好的策略是在需要 Math.random
Math.useSeed
函数
您可以随时使用 jest-mock-random
但它比第一个答案中提出的模拟它提供了更多的功能。
例如,您可以在 testmockRandomWith(0.6);
之前使用,而您在测试中的 Math.random 将始终 return 这个可预测值
模拟文字随机数据并不是真正的测试方式。如前所述,问题有点宽泛,因为 "how to test a function that output is random" 要求您对输出进行统计分析以确保有效的随机性 - 这可能是由伪随机数生成器的创建者完成的。
推断而不是 "that output is random" 意味着你想确保函数正常运行而不管随机数据然后只是模拟 Math.random 调用 return 符合你特定标准的数字(涵盖任何差异)就足够了。该功能是第三方边界,虽然需要测试,但不是根据我的推断正在测试的内容。除非是——在这种情况下,请参阅上一段。
我用过:
beforeEach(() => {
jest.spyOn(global.Math, 'random').mockReturnValue(0.123456789);
});
afterEach(() => {
jest.spyOn(global.Math, 'random').mockRestore();
})
在测试之外添加和恢复功能很容易。