繁体   English   中英

在函数内模拟函数并开玩笑地获取调用计数

[英]mocking a function inside a function and getting calls count in jest

考虑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
});

我很感激你试图理解这个问题的jest ,所以我会尽我所能回答。 我通常建议您不要模拟函数,除非您对服务器或数据库或其他任何东西进行某种复杂的后端调用。 您可以使用真实代码(而不是模拟)进行的更真实的测试会更加有益。 这将是我对你前进的建议。

但就这个问题而言 - 这就是正在发生的事情。

你没有看到你的模拟在这里以正确的方式工作的原因是因为你基本上创建了两个单独的模拟:

  • 您已经为randomizeArr创建了一个模拟。
  • 您已经为randomizeRange创建了另一个单独的模拟。

当您在测试中调用randomArrMock(100, 20, 1000)时,这将按照您的指定为randomizeArr调用您的单个模拟,但该模拟绝对没有任何存在的randomizeRange模拟的概念。 所以它永远不会在幕后被调用。 所以这就是为什么你在最后看到呼叫数量为0原因。

只需在此处稍微更改您的设置以指定调用哪些模拟以及何时调用,以便按照您的期望看到两者一起工作。

我在这里实际选择的根本不是模拟您的randomizeRange方法。 在这种情况下,该方法调用了Math.floor ,这将更容易“监视”(这是我们将在此处使用的方法的提示)。 每次调用randomizeRange都会调用一次Math.floor方法,因此它将是一个理想的选择。 它也应该被调用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)
});

现在这将通过,因为每次调用randomizeRange方法时都会调用Math.floor

最后不要忘记使用以下命令在测试完成后恢复原始Math.floor功能:

mathFloorSpy.mockRestore();

测试愉快!

这似乎是一个常见问题,我能找到的最“官方”解决方案是在 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);
});

使用spyOntoHaveBeenCalledTimes可以提高这里的可读性。


现在,给我自己的建议:编写可测试的代码。 如果您的代码是可测试的,那么您可以轻松地为其编写测试,并且可测试的代码通常更加模块化和灵活; 但不要专注于测试实现细节。

在您的示例中,如果您想公开一个randomArr方法,则不要同时公开randomizeRange 您的randomArr方法可以调用randomizeRange ,但这是您不应该担心的实现细节。

如果您想让您的randomArr更易于测试,请考虑将范围方法(或范围)设为参数。 然后您可以测试它,而无需测试它如何生成随机范围的一些实现。 像这样的东西:

exports.randomArr = (n, min, max, randomRangeGenerator) => {
    const arr = [];
    for (let i = 0; i < n; i++) {
        arr.push(randomRangeGenerator(min, max));
    }
    return arr;
};

您也可以将此模式扩展到randomizeRange 为了测试该方法,要么将floorrandom函数作为参数传递,要么在测试中模拟它们(通过 Math 对象)。 在测试中使用Math.random很困难,因为它会在每次运行时产生不同的值,所以重要的是模拟。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM