Home > database >  Jest mock module functions used in another module
Jest mock module functions used in another module

Time:03-19

I'm new to Jest and trying to write some tests on some existing code. While I've come across lots of Jest articles and Stackoverflow posts on mocking functions within a module (this, for example), I believe I'm trying to pull off something that's either a bit advanced or very stupid and needs code refactoring. Either way, I'm not sure and would really appreciate some help!

Let me explain my situation using an example code. Suppose we're dealing with an e-commerce system, and the backend has a module to fetch order details from some 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,
}

And then there's a very similar module for fetching delivery data of an order:

// 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,
}

As you can see, there's a console.log in each module, which is used to indicate that the original code has been executed instead of the mocked code.

Now, suppose that both of these modules are being used by another module function like this:

// 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,
};

And it's the behavior of this combineOrderData() function that I want to test by mocking out getOrderDataFromApi() and getDeliveryDataFromApi().

For this purpose, I created the following test file:

// 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);
});

When I run now npx jest, I see the console.logs in the output, telling me what the mocking didn't succeed:

  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.

I think this is happening because when combineOrderData() gets called, it instantiates its own copy of the modules used (I thought Node modules were singletons by default; as in, only one copy of imported module exists and is shared by all requires)?

In any case, I don't know what's the proper way to achieve this.

Also, please feel free to comment if the code structure is broken and a refactoring would solve the problem, but I don't see how I can avoid writing and testing code that makes other function calls and so it seems I will run into this problem regularly.

CodePudding user response:

I found 2 issues with the test

  1. When using jest.mock, object should be returned in the callback, in your case parenthesis is missing
jest.mock("./get-delivery-data", () => ({
  getDeliveryDataFromApi: jest.fn(() => Promise.resolve(mockedDelieryData)),
}));
  1. Incorrect order of mocking imports.

First require imports the actual modules instead of mocked ones.

Working test

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

  • Related