【问题标题】:mocking a function inside a function and getting calls count in jest在函数内模拟一个函数并开玩笑地获取调用计数
【发布时间】:2019-11-23 17:06:25
【问题描述】:

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

【问题讨论】:

  • 我能问一下,考虑到它们是非常简单的 JavaScript 方法并且不连接到任何后端 API 或数据库,为什么还要嘲笑这些函数?
  • 只是为了学习 JEST 的工作原理,它是一个例子
  • 我的意思不是消极的,伙计。我只是好奇并问了一个真正的问题,即为什么要模拟一个使用非常简单的 JS 代码并且没有进行任何复杂后端调用的函数。只是问我自己的清晰度!
  • 道歉@AJC24,我误会了!

标签: javascript unit-testing mocking jestjs


【解决方案1】:

感谢您试图通过这个问题来理解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();

测试愉快!

【讨论】:

  • 一个问题,如果我没有更改实现,为什么我必须调用mockRestore()
  • 说实话,在这种情况下你不需要它,但我习惯于总是拆除我在测试中设置的所有模拟。养成一个好习惯。我遇到过模拟相互冲突的情况,因此最好事后将它们拆除以确保不会发生这种情况。
【解决方案2】:

这似乎是一个常见问题,我能找到的最“官方”的解决方案是在 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 很困难,因为它会在每次运行时产生不同的值,因此模拟这一点很重要。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-02-07
    • 2018-12-07
    • 1970-01-01
    • 2023-03-17
    • 2020-04-21
    • 1970-01-01
    • 2019-12-19
    • 1970-01-01
    相关资源
    最近更新 更多