在script.js
中考虑此模块:
const randomizeRange = (min, max) => {
return Math.floor(Math.random() * (max - min) + min);
};
const randomArr = (n, min, max) => {
const arr = [];
for (let i = 0; i < n; i++) {
arr.push(randomizeRange(min, max));
}
return arr;
};
module.exports = { randomArr, randomizeRange };
在下面的测试文件中,randomizeRangeMock
的调用计数不应该为100,因为它在一次调用过的randomArrMock
内部被调用了吗?为什么我得到0
const randomArr = require("./script").randomArr;
const randomizeRange = require("./script").randomizeRange;
const randomArrMock = jest.fn(randomArr);
const randomizeRangeMock = jest.fn(randomizeRange);
test("tests for randomArr", () => {
expect(randomArrMock(100, 20, 1000)).toHaveLength(100);
expect(randomArrMock.mock.calls.length).toBe(1)
expect(randomizeRangeMock.mock.calls.length).toBe(100) //This fails because its 0
});
答案 0 :(得分:1)
我感谢您尝试通过这个问题来理解ThreadPool
,因此,我将尽力回答。我通常建议您不要模拟函数,除非您要对服务器或数据库进行某种复杂的后端调用。您可以使用实际代码(而不是模拟)执行的更真实的测试将无限地受益。那将是我前进的秘诀。
但是出于这个问题的目的-这是正在发生的事情。
之所以无法在此处看到正确的模拟效果,是因为您实际上已经创建了两个单独模拟:
jest
创建了一个模拟。randomizeArr
创建了另一个单独的模拟。在测试中调用randomizeRange
时,这将为您指定的randomArrMock(100, 20, 1000)
调用单个模拟,但是该模拟绝对没有randomizeArr
的其他模拟的概念现有。因此,它从未在后台调用。因此,这就是为什么您在通话结束时看到randomizeRange
的原因。
只需在此处稍微更改一下设置即可指定要调用哪些模拟程序,以及何时才能使两者按预期工作在一起。
我实际上在这里选择的是不来完全模拟您的0
方法。在这种情况下,该方法调用了randomizeRange
,这将更容易“监视”(这是我们将在此处使用的方法的提示)。每次调用Math.floor
都会调用一次Math.floor
方法,因此这是一个理想的选择。它也应该被调用randomizeRange
次,与您的方法相同。
我们选择使用100
方法而不是按照您已经做过的方式使用实际模拟的原因是,因为我们想保留原始行为,因此监视该方法有多少次调用而不覆盖预期的行为是理想的。
因此,请按以下步骤保留spyOn
的现有模拟:
randomizeArr
现在为const randomArrMock = jest.fn(randomArr);
设置间谍
Math.floor
此“间谍”将有效监视此方法,而不会覆盖其行为。如果需要,您可以覆盖该行为,但是我们希望在保持行为不变的同时,只计算该行为被调用的次数。
现在,当您运行测试套件时:
const mathFloorSpy = jest.spyOn(Math.prototype, `floor`);
现在将通过,因为每次调用test("tests for randomArr", () => {
expect(randomArrMock(100, 20, 1000)).toHaveLength(100);
expect(randomArrMock.mock.calls.length).toBe(1)
expect(mathFloorSpy.mock.calls.length).toBe(100)
});
方法时都会调用Math.floor
。
最后,不要忘记使用以下方法来恢复测试的原始randomizeRange
功能:
Math.floor
测试愉快!
答案 1 :(得分:1)
这似乎是一个常见问题,我能找到的最“官方”解决方案是在Jest GitHub的问题页面,https://github.com/facebook/jest/issues/936#issuecomment-545080082。
由于您使用的是require
而不是使用babel编译的ES模块,因此该链接问题中描述的解决方案似乎也不适用。
我将修改您的script.js
模块以直接引用使用的导出,以便模拟引用正确的函数:
exports.randomizeRange = (min, max) => {
return Math.floor(Math.random() * (max - min) + min);
};
exports.randomArr = (n, min, max) => {
const arr = [];
for (let i = 0; i < n; i++) {
arr.push(exports.randomizeRange(min, max));
}
return arr;
};
然后您的测试实现将引用模拟的函数:
const myScript = require('../script');
jest.spyOn(myScript, 'randomArr');
jest.spyOn(myScript, 'randomizeRange');
test("tests for randomArr", () => {
expect(myScript.randomArr(100, 20, 1000)).toHaveLength(100);
expect(myScript.randomArr).toHaveBeenCalledTimes(1);
expect(myScript.randomizeRange).toHaveBeenCalledTimes(100);
});
在这里使用spyOn
和toHaveBeenCalledTimes
可以提高可读性。
现在,给我自己一条建议:编写可测试的代码。如果您的代码是可测试的,那么您可以轻松地为其编写测试,并且可测试的代码通常更具模块化和灵活性。但不要专注于测试实施细节。
在您的示例中,如果您想公开randomArr
方法,那么也不要公开randomizeRange
;您的randomArr
方法可以调用randomizeRange
,但这是您不需担心的实现细节。
如果您想更轻松地测试randomArr
,请考虑将range方法(或range)设为参数。然后,您可以测试它,而无需测试如何生成随机范围的某些实现。像这样:
exports.randomArr = (n, min, max, randomRangeGenerator) => {
const arr = [];
for (let i = 0; i < n; i++) {
arr.push(randomRangeGenerator(min, max));
}
return arr;
};
您也可以将此模式扩展到randomizeRange
。为了测试该方法,可以将floor
和random
函数作为参数传递,或者在测试中模拟它们(通过Math对象)。在测试中使用Math.random
很困难,因为每次运行都会产生不同的值,因此进行模拟非常重要。