使用 Jest 在函数数组上只模拟一个函数

Mocking just one function on a function array using Jest

这是我的函数

//functions.js
const functions =
{
    add: (a, b) => a + b,
    average: (a, b) => functions.add(a, b) / 2
}


module.exports = functions;

这是我的测试,

jest.mock("./functions"); // this happens automatically with automocking

const functions = require("./functions");

test('calculate the average', () => {

    functions.add.mockImplementation((a, b) => a + b);
    expect(functions.average(2, 2)).toBe(2);

})

我知道如何在 NestJS 上做到这一点,至少对于我所处的环境来说是这样。我正在准备一个 Udemy 教程,我想做一个简单的例子。 我希望能够仔细检查方法 add 是否被正确调用,参数是否被正确调用。

问题是自动模拟正在模拟一切。我只想模拟添加功能,因为我知道发生了什么。

我能够找到这个解决方案,但我觉得在 Jest 中尝试转换理论时缺少一些东西。

//jest.mock("./functions"); // this happens automatically with automocking

const functions = require("./functions");

test('calculate the average', () => {

    const spy = jest.spyOn(functions, 'add').mockImplementation((a, b) => console.log("I was called"));
    //functions.add.mockImplementation((a, b) => a + b);
    functions.average(2, 2)
    //expect(functions.average(2, 2)).toBe(2);
    expect(spy.mock.calls[0][1]).toBe(2);
})

尝试实施建议的解决方案,这是有道理的,但是,它并没有像我预期的那样工作。我无法访问模拟属性。这个想法是应该存储信息,就像传统模拟一样,但是,我如何访问?

jest.mock('./functions', () => {
    // Require the original module to not be mocked...
    const originalModule = jest.requireActual('./functions');

    return {
        __esModule: true, // Use it when dealing with esModules
        ...originalModule,
        add: jest.fn((a, b) => a + b),
    };
});

const functions = require("./functions");

test('calculate the average', () => {

    const add = require("./functions").add;

    functions.average(2, 2);
    expect(add.mock.calls[0][1]).toBe(2);

})

也尝试过:

test('calculate the average', () => {

    const add = require("./functions").add;
    expect(functions.average(2, 2)).toBe(2);
})

这告诉我模拟是“本地的”,即使在模拟之后,average 也在调用正常方法,在间谍的情况下,它会调用模拟的方法。似乎唯一的解决办法是间谍后果。

P.S。我更改了模拟函数以证明调用了哪个函数。

尝试这样的事情。这只会模拟添加功能。

jest.mock("./functions", () => ({
    ...jest.requireActual("./functions"),
    add: jest.fn(() => {}) // Pass your mock implementation as param in jest.fn() 
}));

const functions = require("./functions");

更新

如果你也想使用实际功能

describe("Mocking dependent functions", () => {
    afterEach(() => {
        jest.resetModules()
    });

    it('average should be 400', () => {
        const functions = jest.requireActual('./function');
        functions.add = jest.fn(() => 800); // using mock
        expect(functions.average(2, 2)).toBe(400);
    });
    
    it('average should be 2', () => {
        const functions = jest.requireActual('./function'); // using actual function
        expect(functions.average(2, 2)).toBe(2);
    });

    it('average should be 5', () => {
        const functions = jest.requireActual('./function');
        functions.add = jest.fn(() => 10); // using mock
        expect(functions.average(2, 2)).toBe(5);
    });
});

如果只想使用mock函数

const functions = jest.requireActual('./function');
describe("Mocking dependent functions", () => {
    it('average should be 400', () => {
        functions.add = jest.fn(() => 800); // using mock
        expect(functions.average(2, 2)).toBe(400);
    });

    it('average should be 5', () => {
        functions.add = jest.fn(() => 10); // using mock
        expect(functions.average(2, 2)).toBe(5);
    });
})

在第一种方法中,您可能还想使用实际函数,Jest 无法清除模拟,即为什么我必须在每个测试用例中以及在每个测试用例之后都需要函数文件测试用例我必须重置模块。

还有另一种方法可以解决这个问题,但我不喜欢那样。仍然添加代码供您参考。在这里您可以保留一份原始函数以备后用

const functions = jest.requireActual('./function');
const originalFunctions = { ...functions };

describe("Mocking dependent functions", () => {
    it('average should be 400', () => {
        functions.add = jest.fn(() => 800); // using mock
        expect(functions.average(2, 2)).toBe(400);
    });
    
    it('average should be 2', () => {
        functions.add = originalFunctions.add; // getting actual function from copy
        expect(functions.average(2, 2)).toBe(2);
    });
});

记住:当您调用 require() 时,您会得到一个引用模块函数的对象。所以当你覆盖函数时,总是试图恢复原来的状态。因此 jest.resetModule() 方法是推荐的方法。

您正在寻找的测试(“只是模拟 add 函数”)是这个(您可以看到它有效 here):

const functions = require('./functions');

jest.mock('./functions', () => ({
  ...jest.requireActual('./functions'),
  add: jest.fn((a, b) => a + b),
}));

test('calculate the average', () => 
    expect(functions.average(2, 2)).toBe(2)
);

正如 Sandeep 强调的那样,测试的关键部分是 jest.requireActual. jest.mock in this example is using the module factory parameter to return the mock module used for the test. Within the module factory, javascript object literal spread syntax 用于创建新对象,即

{
  ...jest.requireActual('./functions'),
  add: jest.fn((a, b) => a + b),
}

具有实际方法(即 addaverage),但 add 函数正在被模拟版本替换,即 jest.fn((a, b) => a + b).