Jest manual mock of named class export

Viewed 583

I'm trying to do a manual mock of a named ES6 class export, but I'm not if it's just not possible, or if I'm doing something wrong. I would like to do it this way so that the same mocks can be imported across several different test files rather than having to duplicate them in each test file. Here is a simplified example of what I want to do:

File structure:

.
└── test
    ├── __mocks__
        └── test.service.ts 
    ├── __tests__
        └── test.consumer.spec.ts 
    ├── test.service.ts
    └── test.consumer.ts

test.service.ts

export default class TestService {
  private readonly field: string;

  constructor(field: any) {
    this.field = field;
  }

  foo() {
    console.log(this.field);
  }
}

test.consumer.ts

import TestService from './test.service';

export default {
  useService: () => {
    const service = new TestService('Field value');
    service.foo();
    return true;
  },
};

__ mocks __/test.service.ts

export const mockFoo = jest.fn();

const mock = jest.fn().mockImplementation(() => {
  return { foo: mockFoo };
});

export default mock;

__ tests __/test.consumer.spec.ts

import TestConsumer from '../test.consumer';
import MockService, { mockFoo } from '../__mocks__/test.service';

jest.mock('../test.service');

describe('TestConsumer...', () => {
  beforeEach(() => {
    MockService.mockClear();
    mockFoo.mockClear();
  });
  test('It works', () => {
    const val = TestConsumer.useService();
    expect(val).toEqual(true);
    expect(mockFoo).toHaveBeenCalledTimes(1);
    expect(MockService).toHaveBeenCalledTimes(1);
  });
});

When running the test, I get this output:

 FAIL  src/test/__tests__/test.consumer.spec.ts
  TestConsumer...
    × It works (3 ms)

  ● TestConsumer... › It works

    expect(jest.fn()).toHaveBeenCalledTimes(expected)

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

      12 |     const val = TestConsumer.useService();
      13 |     expect(val).toEqual(true);
    > 14 |     expect(mockFoo).toHaveBeenCalledTimes(1);
         |                     ^
      15 |     expect(MockService).toHaveBeenCalledTimes(1);
      16 |   });
      17 | });

      at Object.<anonymous> (src/test/__tests__/test.consumer.spec.ts:14:21)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        3.685 s, estimated 4 s

I can confirm that it is the real foo function that is being called. I've tried changing the way I import MockService and mockFoo to match the docs here by changing it to import MockService, { mockFoo } from '../test.service'; but then it complains that mockFoo is not exported.

Any help/suggestions are appreciated

2 Answers

Importing from __mocks__ doesn't seem to be a good idea. You should initialize a mock function from the test suite and manage the function from there.

import TestConsumer from '../test.consumer';
import MockService from '../test.service';

jest.mock('../test.service');

describe('TestConsumer...', () => {
  const mockFoo = jest.fn();

  beforeAll(() => {
    MockService.mockImplementation(() => ({ foo: mockFoo }));
  });

  beforeEach(jest.clearAllMocks);

  test('It works', () => {
    const val = TestConsumer.useService();
    expect(val).toEqual(true);
    expect(mockFoo).toHaveBeenCalledTimes(1);
    expect(MockService).toHaveBeenCalledTimes(1);
  });
});

This way you won't need __mocks__/test.service.ts at all.


EDIT: Importing from the module to be mocked will work (rather than from __mocks__).

import TestConsumer from '../test.consumer';
import MockService, { mockFoo } from '../test.service';

jest.mock('../test.service');

describe('TestConsumer...', () => {
  beforeEach(jest.clearAllMocks);

  test('It works', () => {
    const val = TestConsumer.useService();
    expect(val).toEqual(true);
    expect(mockFoo).toHaveBeenCalledTimes(1);
    expect(MockService).toHaveBeenCalledTimes(1);
  });
});

I'm just in the middle of learning how to use jest properly.

I've tried changing the way I import MockService and mockFoo to match the docs here by changing it to import MockService, { mockFoo } from '../test.service'; but then it complains that mockFoo is not exported.

I think the doc is wrong. Try..

__ tests __/test.consumer.spec.ts

import MockService  from '../test.service';
...
expect((jest.mocked(MockService).mock.results[0].value as TestService).foo).toHaveBeenCalledTimes(1);

Using manual mocks where the mock is being instantiated inside the test subject seems to awkward. Means you have to know and fetch the mock instance that has been instatiated and then access the mock functions within. Library doesnt seem to support this well.

Would suggest using manual mock only for modules that are exporting pre-created instances and functions.

Related