Home > Enterprise >  Jest how to assert method calls on mocked classes
Jest how to assert method calls on mocked classes

Time:11-11

I'm wanting to mock the implementation of a class, but then perform assertions that the methods have been called in my test.

The below results in:

Error: expect(jest.fn()).toHaveBeenCalled()

Expected number of calls: >= 1
Received number of calls:    0

I'm assuming whats not working here is my call to const service = new Service(); in the test file is creating a new instance of the mock.

How can I mock classes, but ensure the same instance is returned when it's instantiated elsewhere in the test?

// ./service.ts

class Service {
  getNothing() {
    return null;
  }
}

export { Service };
// ./controller.ts

import { Service } from './service';

export default async (): Promise<void> => {
  const service = new Service();

  console.log(`result: ${service.getNothing()}`);
};
// ./example.test.ts
import { Service } from './service';
import controller from './controller';

jest.mock('./service', () => {
  return {
    Service: jest.fn().mockImplementation(() => {
      return {
        getNothing: jest.fn(),
      };
    }),
  };
});

describe('example test', () => {
  test('example', async () => {
    const service = new Service();

    await controller();

    expect(service.getNothing).toHaveBeenCalled();
  });
});

CodePudding user response:

It looks like you're calling the mocked constructor several times, and getting back several different instances of the mocked object. This means that the expect(service.getNothing) is referring to a different method than the service.getNothing created in the controller.

Instead, you need to make sure that the same function is used to mock all instances of getNothing. Luckily this is easy to do:

// ./example.test.ts
import { Service } from './service';
import controller from './controller';

// create our mock to assert on later
const mockedGetNothing = jest.fn();

jest.mock('./service', () => {
  return {
    Service: jest.fn().mockImplementation(() => {
      return {
        // return the same mock function for all instances of the module
        getNothing: mockedGetNothing,
      };
    }),
  };
});

describe('example test', () => {
  test('example', async () => {
    const service = new Service();

    await controller();

    // assert on the mocked function
    expect(mockedGetNothing).toHaveBeenCalled();
  });
});

Note that jest will automatically hoist all module mocking code to the top of the file, before any imports, due to the way the module mocks work behind the scenes. This can cause issues if the mocks use variables defined in the test file, although as far as I'm aware any variables with names starting with mock should be hoisted as well.

  • Related