Jest模拟在另一个模块中使用的模块函数

4

我是 Jest 的新手,正尝试在一些现有的代码中编写一些测试。虽然我已经看到很多关于 Jest 和模块内部函数模拟的文章和 Stackoverflow 帖子(例如 this),但我相信我正在尝试完成某些比较高级或非常愚蠢的操作,并需要重构代码。无论哪种情况,我都不确定,真的希望能得到一些帮助!

让我使用一个示例代码来解释我的情况。假设我们正在处理一个电子商务系统,在后端有一个模块从某个 API 获取订单详情:

// get-order-data.js

const getOrderDataFromApi = async (orderId) => {
    // . . . some API call
    console.log("The actual getOrderData function called!");
}

// Added only to emphasize that the entire module shouldn't be mocked
const getOrderDataFromDatabase = async (orderId) => { }

module.exports = {
    getOrderDataFromApi,
    getOrderDataFromDatabase,
}

然后还有一个非常类似的模块,用于获取订单的交付数据:

// get-delivery-data.js

const getDeliveryDataFromApi = async (orderId) => {
    // . . . some API call
    console.log("The actual getDeliveryDataFromApi function called!");
}

// Added only to emphasize that the entire module shouldn't be mocked
const getDeliveryDataFromDatabase = async (orderId) => { }

module.exports = {
    getDeliveryDataFromApi,
    getDeliveryDataFromDatabase,
}

正如您所看到的,每个模块中都有一个console.log,用于指示已执行原始代码而不是模拟代码。

现在,假设这两个模块都被另一个模块函数使用,如下所示:

// combine-order-data.js
const { getDeliveryDataFromApi } = require("./get-delivery-data");
const { getOrderDataFromApi } = require("./get-order-data")

const combineOrderData = async (orderId) => {
    const orderData = await getOrderDataFromApi(orderId);
    const deliveryData = await getDeliveryDataFromApi(orderId);

    return {
        orderData,
        deliveryData,
    };
}

module.exports = {
    combineOrderData,
};

我想要测试的是combineOrderData()函数的行为,通过模拟getOrderDataFromApi()getDeliveryDataFromApi()来实现。

为此,我创建了以下测试文件:

// order.test.js

const { combineOrderData } = require("./combine-order-data");

const mockedOrderData = {
    id: 1,
    amount: 1000,
};

const mockedDelieryData = {
    orderId: 1,
    status: 'DELIVERED',
};

beforeEach(() => {
    jest.mock("./get-order-data", () => {
        getOrderDataFromApi: jest.fn(() => Promise.resolve(mockedOrderData))
    });
    jest.mock("./get-delivery-data", () => {
        getDeliveryDataFromApi: jest.fn(() => Promise.resolve(mockedDelieryData))
    });
});

test("combineOrderData correctly combines data", async () => {
    const combinedOrder = await combineOrderData(111);
});

当我现在运行npx jest时,我可以在输出中看到console.log,告诉我模拟没有成功:

  console.log
    The actual getOrderData function called!

      at getOrderDataFromApi (get-order-data.js:3:13)

  console.log
    The actual getDeliveryDataFromApi function called!

      at getDeliveryDataFromApi (get-delivery-data.js:3:13)

 PASS  ./order.test.js
  ✓ combineOrderData correctly combines data (28 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        0.227 s
Ran all test suites related to changed files.

我认为这是因为当调用combineOrderData()时,它会实例化自己使用的模块的副本(我以为Node模块默认是单例的;也就是说,只有一个导入模块的副本存在,并由所有require共享)。

无论如何,我不知道实现这一点的正确方法。

此外,请随意评论代码结构是否有问题,重构是否可以解决问题,但我不知道如何避免编写和测试调用其他函数的代码,因此似乎我会经常遇到这个问题。


3
感谢您发布问题并且详细描述了整个情况! - Marco Arruda
@MarcoArruda 不用谢。由于这一切,我最终采取的一个做法是每个文件只有一个函数,无论代码库变得多么愚蠢。因此,当模拟整个模块时,我不需要担心保留其他函数定义。 ‍♂️ - ankush981
1个回答

5

我发现这个测试有两个问题

  1. 使用jest.mock时,在回调函数中应该返回对象,但在您的情况下缺少括号
jest.mock("./get-delivery-data", () => ({
  getDeliveryDataFromApi: jest.fn(() => Promise.resolve(mockedDelieryData)),
}));
  1. 模拟导入库的顺序不正确。

第一个 require 导入了实际的模块而不是模拟的模块。

正常运行的测试

jest.mock("./get-order-data", () => ({
  getOrderDataFromApi: jest.fn(() => Promise.resolve(mockedOrderData)),
}));
jest.mock("./get-delivery-data", () => ({
  getDeliveryDataFromApi: jest.fn(() => Promise.resolve(mockedDelieryData)),
}));
const { combineOrderData } = require("./combine-order-data");

const mockedOrderData = {
  id: 1,
  amount: 1000,
};

const mockedDelieryData = {
  orderId: 1,
  status: "DELIVERED",
};

test("combineOrderData correctly combines data", async () => {
  const combinedOrder = await combineOrderData(111);
});


感谢您的辛勤工作!是的,导入顺序是我犯的一个重大错误。另一件事是语法。传递给 jest.mock 的函数需要返回一个对象,显然,所以箭头函数语法需要 () => ({}) 而不是 () => {}。再次感谢! :-) - ankush981

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接