Search code examples
node.jsunit-testingjestjsts-jesttestcase

How to clear/reset mockimplementation for a method using Jest


Using Jest I've mockimplemented the method and testcase is getting succeeded and for testcase2 I need to implement same mock method with different input and I'm unable to do that.

jest.mock('../../lib/testService', () => ({
  TestService : jest.fn(() => ({
      callAPIProd: jest.fn().mockImplementation(() => Promise.resolve([{ DateTime: '2022-01-01'}])),
      getAuthToken: jest.fn().mockImplementation(() => Promise.resolve({ data: 'mock token' })),
      checkIfExistsInDB: jest.fn().mockImplementation(() => Promise.resolve(false)),
  })),
}));
describe('getRates', () => {
    it('should insert rates into DB if not already available', async () => {
      // Arrange
      const req = {} as Request;
      const res = {
        send: jest.fn(),
      } as unknown as Response;

      // Act
      await integration.getRates(req, res);

      // Assert
      expect(res.send).toHaveBeenCalledWith('New Rates Received');
    });

    it('should send response if rates are already available in DB', async () => {
      // Arrange
      const req = {} as Request;
      const res = {
        send: jest.fn(),
      } as unknown as Response;

      // Act
      await integration.getRates(req, res);

      // Assert
      expect(res.send).toHaveBeenCalledWith('Rates already available in DB');
    });
});

In my getRates method I'm calling the "checkIfExistsInDB" service method which ret=turns true/false. First testcase is getting succeeded as I've mocked the implementation with false value. I want to clear the mockimplementation and set new mockimplementation with true value. How to achieve that?

Tried by re-defining the mock implementation in specific testcase

    it('should send response if rates are already available in DB', async () => {
  // Arrange
  const req = {} as Request;
  const res = {
    send: jest.fn(),
  } as unknown as Response;

  //#1 - Tried to re-define in this way but it didn't work it's still taking value as false
  jest.mock('../../lib/testService', () => ({
    TestService : jest.fn(() => ({
        callAPIProd: jest.fn().mockImplementation(() => Promise.resolve([{ DateTime: '2022-01-01'}])),
        getAuthToken: jest.fn().mockImplementation(() => Promise.resolve({ data: 'mock token' })),
        checkIfExistsInDB: jest.fn().mockImplementation(() => Promise.resolve(true)),
    })),
  }));

  //#2 - Tried to re-define in this way but it didn't work it's still taking value as false
  TestService.prototype.checkIfExistsInDB = jest.fn().mockImplementation(() => Promise.resolve(true));

  // Act
  await integration.getRates(req, res);

  // Assert
  expect(res.send).toHaveBeenCalledWith('Rates already available in DB');
});

Solution

  • jest.mock at top level is hoisted, this is the way it affects import statement. jest.mock inside a test would result in implications, the whole module graph needs to be reimported inside a test, like this.

    Mocking TestService.prototype.checkIfExistsInDB won't work because TestService is mocked function and doesn't deal with prototypes. In order to access checkIfExistsInDB in tests it needs to be exposed in the scope of test suite. Adding an import similarly to mockGetName in this example allows to avoid possible race conditions caused by jest.mock hoisting:

    import { mockedCheckIfExistsInDB  } from '../../lib/testService'
    
    jest.mock('../../lib/testService', () => {
      const mockedCheckIfExistsInDB = jest.fn();
    
      return {
         __esModule: true,
         mockedCheckIfExistsInDB,
         TestService : jest.fn(() => ({
           ...
           checkIfExistsInDB: mockedCheckIfExistsInDB,
         })
      };
    });
    
    beforeEach(() => {
      mockedCheckIfExistsInDB.mockResolvedValue(false)
    });
    
    it('...', async () => {
      ...
      mockedCheckIfExistsInDB.mockResolvedValue(true)
      ...
    });
    

    A test that redefines checkIfExistsInDB mocked value shouldn't affect other tests, but this will happen if default mocked value is defined in jest.mock. It needs to be defined before each test instead.