JestJs: Multiple asynchronous API calls to Axios get mock implementation gives same response

I have a method to get product data from an API. I use Axios to call the API. Since the product data is independent of each other, I call all the APIs at once and use Promise.all to wait for the calls to complete before proceeding. For example, consider a function (not enclosed in a function statement) with below lines.

const productIds = [ "SKU-1", "SKU-2" ];
const promises = productIds.map<Promise<Product>>(id => axios.get(`/products/${id}`));
Promise.all(promises).then(values => {
  // do some stuff here.
});

The unit test case written uses jest mock implementation as below.

import http from "axios";
import { sku1, sku2 } from "./test-product-data";
jest.mock("axios", () => ({
  create: jest.fn(() => http),
  get: jest.fn(() => Promise.resolve()),
}));
const httpMock = http as jest.Mocked<typeof http>;

describe("Update operations!", () => {
  beforeAll(() => {
    httpMock.get.mockImplementation(url => {
      // Call to Get product.
      if (url.endsWith("/SKU-1")) {
        mockedSuccessResponse.data = Object.assign({}, sku1);
        return Promise.resolve(mockedSuccessResponse);
      };
      if (url.endsWith("/SKU-2")) {
        mockedSuccessResponse.data = Object.assign({}, sku2);
        return Promise.resolve(mockedSuccessResponse);
      };
    });
  });
});

Jest returns the response of the last API call, i.e. the response of SKU-2 even for the product with id SKU-1. Am I missing something?

Upvotes: 1

Views: 1741

Answers (1)

Lin Du
Lin Du

Reputation: 102257

Since you are modifying the same object reference(mockedSuccessResponse.data), subsequent changes will overwrite the previous ones. This means the mocked object(sku2) for the url ends with /SKU-2 will override the mockedSuccessResponse.data. When you call axios.get() in promise.all, both of them resolved the sku2 mock data.

Solution: Create different mock objects for different conditional branches.

E.g.

index.ts:

import axios from 'axios';

interface Product {
  id: string;
}

export function main() {
  const productIds = ['SKU-1', 'SKU-2'];
  const promises = productIds.map<Promise<Product>>((id) => axios.get(`/products/${id}`));
  return Promise.all(promises);
}

index.test.ts:

import http from 'axios';
import { main } from './';

jest.mock('axios', () => ({
  create: jest.fn(() => http),
  get: jest.fn(),
}));
const httpMock = http as jest.Mocked<typeof http>;

describe('Update operations!', () => {
  const sku1 = { id: 'sku-1' };
  const sku2 = { id: 'sku-2' };

  beforeAll(() => {
    httpMock.get.mockImplementation((url: string): any => {
      if (url.endsWith('/SKU-1')) {
        return Promise.resolve({ data: Object.assign({}, sku1) });
      }
      if (url.endsWith('/SKU-2')) {
        return Promise.resolve({ data: Object.assign({}, sku2) });
      }
    });
  });

  test('should pass', async () => {
    const actual = await main();
    expect(actual).toEqual([{ data: { id: 'sku-1' } }, { data: { id: 'sku-2' } }]);
  });
});

Test result:

 PASS  examples/70599956/index.test.ts (9.178 s)
  Update operations!
    ✓ should pass (2 ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        9.216 s

Upvotes: 1

Related Questions