[英]mocking a function inside a function and getting calls count in jest
Considering this module in script.js
:考虑
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 };
In the test file below, shouldn't the call count of randomizeRangeMock
be 100 because it is being called inside randomArrMock
which is called once?在下面的测试文件中,
randomizeRangeMock
的调用计数不应该是 100,因为它是在调用一次的randomArrMock
内部调用的吗? Why do I get 0为什么我得到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
});
I appreciate that you are trying to just understand jest
with this question and so I will answer as best I can.我很感激你试图理解这个问题的
jest
,所以我会尽我所能回答。 I'd normally recommend that you don't mock functions unless you are making some kind of complex back end call to a server or database or whatever.我通常建议您不要模拟函数,除非您对服务器或数据库或其他任何东西进行某种复杂的后端调用。 The more real testing you can do with real code (and not mocks) is infinitely more beneficial.
您可以使用真实代码(而不是模拟)进行的更真实的测试会更加有益。 That would be my tip for you going forward.
这将是我对你前进的建议。
But for the purpose of this question - here's what's going on.但就这个问题而言 - 这就是正在发生的事情。
The reason you're not seeing your mocks work in the right way here is because you've essentially created two separate mocks:你没有看到你的模拟在这里以正确的方式工作的原因是因为你基本上创建了两个单独的模拟:
randomizeArr
.randomizeArr
创建了一个模拟。randomizeRange
.randomizeRange
创建了另一个单独的模拟。 When you invoke randomArrMock(100, 20, 1000)
in your test, this will invoke your single mock for randomizeArr
as you've designated but that mock has absolutely no concept of the other mock for randomizeRange
ever existing.当您在测试中调用
randomArrMock(100, 20, 1000)
时,这将按照您的指定为randomizeArr
调用您的单个模拟,但该模拟绝对没有任何存在的randomizeRange
模拟的概念。 And so it's never invoked behind the scenes.所以它永远不会在幕后被调用。 So this is why you see a
0
for the amount of calls at the end.所以这就是为什么你在最后看到呼叫数量为
0
原因。
It's just a matter of changing your setup a little bit here to designate which mocks are invoked and when in order to see both work together as you're expecting.只需在此处稍微更改您的设置以指定调用哪些模拟以及何时调用,以便按照您的期望看到两者一起工作。
What I would actually choose here is not to mock your randomizeRange
method at all.我在这里实际选择的根本不是模拟您的
randomizeRange
方法。 That method invoked Math.floor
and that's going to be much easier to "spy on" (which is a hint for the method we're going to use here) in this case.在这种情况下,该方法调用了
Math.floor
,这将更容易“监视”(这是我们将在此处使用的方法的提示)。 That Math.floor
method is invoked once with each call to randomizeRange
and so it will be an ideal choice.每次调用
randomizeRange
都会调用一次Math.floor
方法,因此它将是一个理想的选择。 It should also be called 100
times, the same as your method.它也应该被调用
100
次,与您的方法相同。
The reason we're choosing to spyOn
the method rather than to use an actual mock in the way you have done already is because we want to keep the original behaviour and so spying on how many times the method is called without overwriting the intended behaviour is ideal.我们选择
spyOn
方法而不是按照您已经完成的方式使用实际模拟的原因是因为我们希望保留原始行为,因此在不覆盖预期行为的情况下监视调用该方法的次数是理想的。
So keep your existing mock for randomizeArr
as follows:因此,请保留您现有的
randomizeArr
模拟,如下所示:
const randomArrMock = jest.fn(randomArr);
Now set up the spy for Math.floor
现在为
Math.floor
设置间谍
const mathFloorSpy = jest.spyOn(Math.prototype, `floor`);
This "spy" will effectively spy on this method without overwriting its behaviour.这个“间谍”将有效地监视此方法而不会覆盖其行为。 You can overwrite the behaviour if you want to but we want to keep the behaviour intact while just counting how many times it is called.
如果您愿意,您可以覆盖该行为,但我们希望保持该行为完整无缺,同时只计算它被调用的次数。
So now when you run your test suite:所以现在当你运行你的测试套件时:
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)
});
This will now pass as Math.floor
will have been invoked each time your randomizeRange
method was called.现在这将通过,因为每次调用
randomizeRange
方法时都会调用Math.floor
。
Finally don't forget to restore the original Math.floor
functionality when your test is finished by using:最后不要忘记使用以下命令在测试完成后恢复原始
Math.floor
功能:
mathFloorSpy.mockRestore();
Happy testing!测试愉快!
This seems to be a common question, and the most "official" solution I can find was on the Jest GitHub issues page, at https://github.com/facebook/jest/issues/936#issuecomment-545080082 .这似乎是一个常见问题,我能找到的最“官方”解决方案是在 Jest GitHub 问题页面上, 网址为https://github.com/facebook/jest/issues/936#issuecomment-545080082 。
Since you are using require
instead of ES modules transpiled with babel, the solutions described in that linked issue don't seem to apply as well.由于您使用的是
require
而不是使用 babel 转译的 ES 模块,因此该链接问题中描述的解决方案似乎也不适用。
I would modify your script.js
module to directly reference the exports used, so that the mocks refer to the correct function:我会修改您的
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;
};
And then your test implementation would reference the mocked functions:然后您的测试实现将引用模拟函数:
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);
});
Using spyOn
and toHaveBeenCalledTimes
improves readability here.使用
spyOn
和toHaveBeenCalledTimes
可以提高这里的可读性。
Now, giving me own piece of advice: Write testable code.现在,给我自己的建议:编写可测试的代码。 If your code is testable, well, you can easily write tests for it, and testable code is generally more modular and flexible;
如果您的代码是可测试的,那么您可以轻松地为其编写测试,并且可测试的代码通常更加模块化和灵活; but don't focus on testing implementation details.
但不要专注于测试实现细节。
In your example, if you want to expose a randomArr
method, then don't also expose randomizeRange
;在您的示例中,如果您想公开一个
randomArr
方法,则不要同时公开randomizeRange
; your randomArr
method can call randomizeRange
, but that is an implementation detail you shouldn't worry about.您的
randomArr
方法可以调用randomizeRange
,但这是您不应该担心的实现细节。
If you want to make your randomArr
much easier to test, consider making the range method (or the range) a parameter.如果您想让您的
randomArr
更易于测试,请考虑将范围方法(或范围)设为参数。 You can then test it without testing some implementation of how it generates random ranges.然后您可以测试它,而无需测试它如何生成随机范围的一些实现。 Something like this:
像这样的东西:
exports.randomArr = (n, min, max, randomRangeGenerator) => {
const arr = [];
for (let i = 0; i < n; i++) {
arr.push(randomRangeGenerator(min, max));
}
return arr;
};
You can extend this pattern to randomizeRange
as well.您也可以将此模式扩展到
randomizeRange
。 For testing that method, either pass in the floor
and random
functions as parameters, or mock them (through the Math object) in your tests.为了测试该方法,要么将
floor
和random
函数作为参数传递,要么在测试中模拟它们(通过 Math 对象)。 Working with Math.random
in tests is difficult since it will produce different values on each run, so it's important mock that.在测试中使用
Math.random
很困难,因为它会在每次运行时产生不同的值,所以重要的是模拟。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.