20

I have the following typescript class which I want to test in Jest.

//MyClass.ts
import { foo } from './somewhere/FooFactory';
export class MyClass {
  private _state : number;
  constructor( arg : string ) {
    this._state = foo( arg );
  }

  public getState() : string {
    return this._state;
  }
}

This is my test:

//MyClass.spec.ts
import { MyClass } from './MyClass';
describe( 'test MyClass', () => {
  test( 'construct' => {
    const c = new MyClass( 'test' );
    expect( c ).toBeDefined();
    expect( c.getState() ).toEqual( 'TEST' );
  } );
} );

How do I mock the foo function used inside MyClass so that this test passes?

skyboyer
  • 22,209
  • 7
  • 57
  • 64
Marek Krzeminski
  • 1,308
  • 3
  • 15
  • 40
  • looks like the same as https://stackoverflow.com/questions/40465047/how-can-i-mock-an-es6-module-import-using-jest – Alejandro Nov 11 '18 at 12:49

1 Answers1

59

There are a few different ways to approach it.


You can mock only foo using jest.spyOn and something like mockImplementation:

import { MyClass } from './MyClass';
import * as FooFactory from './somewhere/FooFactory';

describe('test MyClass', () => {
  test('construct', () => {
    const mock = jest.spyOn(FooFactory, 'foo');  // spy on foo
    mock.mockImplementation((arg: string) => 'TEST');  // replace implementation

    const c = new MyClass('test');
    expect(c).toBeDefined();
    expect(c.getState()).toEqual('TEST');  // SUCCESS

    mock.mockRestore();  // restore original implementation
  });
});

Similarly, you can auto-mock FooFactory with jest.mock, then provide an implementation for foo:

import { MyClass } from './MyClass';
import * as FooFactory from './somewhere/FooFactory';

jest.mock('./somewhere/FooFactory');  // auto-mock FooFactory

describe('test MyClass', () => {
  test('construct', () => {
    const mockFooFactory = FooFactory as jest.Mocked<typeof FooFactory>;  // get correct type for mocked FooFactory
    mockFooFactory.foo.mockImplementation(() => 'TEST');  // provide implementation for foo

    const c = new MyClass('test');
    expect(c).toBeDefined();
    expect(c.getState()).toEqual('TEST');  // SUCCESS
  });
});

You can also mock FooFactory using a module factory passed to jest.mock:

import { MyClass } from './MyClass';

jest.mock('./somewhere/FooFactory', () => ({
  foo: () => 'TEST'
}));

describe('test MyClass', () => {
  test('construct', () => {
    const c = new MyClass('test');
    expect(c).toBeDefined();
    expect(c.getState()).toEqual('TEST');  // SUCCESS
  });
});

And finally, if you plan to use the same mock across multiple test files you can mock the user module by creating a mock at ./somewhere/__mocks__/FooFactory.ts:

export function foo(arg: string) {
  return 'TEST';
}

...then call jest.mock('./somewhere/FooFactory'); to use the mock in the test:

import { MyClass } from './MyClass';

jest.mock('./somewhere/FooFactory');  // use the mock

describe('test MyClass', () => {
  test('construct', () => {
    const c = new MyClass('test');
    expect(c).toBeDefined();
    expect(c.getState()).toEqual('TEST');  // SUCCESS
  });
});
Brian Adams
  • 43,011
  • 9
  • 113
  • 111
  • 3
    I logged into stackoverflow just to upvote this answer. Thank you very much. – Brijesh Shah Jul 13 '20 at 12:19
  • 3
    This was so helpful! – callback May 19 '21 at 06:38
  • 1
    How about when `MyClass` and `foo` are inside the same module? – Emre Tapcı Dec 02 '22 at 13:48
  • 1
    @EmreTapcı see https://stackoverflow.com/a/55193363/10149510 (TLDR: import the module into itself and reference `foo` within `MyClass` using the module, then you can mock `foo` using the module) – Brian Adams Dec 02 '22 at 22:07
  • @BrianAdams that is, without having to modify the source code. – Emre Tapcı Dec 05 '22 at 11:02
  • 1
    @EmreTapcı mocking relies on a level of indirection so if `MyClass` and `foo` are in the same module and `MyClass` calls `foo` directly then there isn't any indirection and that call to `foo` can't be mocked without first modifying the source code. (basically there isn't a place for a mock to squeeze in and redirect the call to something else) – Brian Adams Dec 05 '22 at 23:15