Search code examples
typescriptunit-testingjestjsts-jest

Mock object creation using jest


I am very new to jest testing. I have below implementation class

import { ExternalObject } from 'external-library';

export class MyClass {
  public createInstance(settings : ISettings) : ExternalObject {
    const setting1 = settings.getSetting("setting1");
    const setting2 = settings.getSetting("setting2");
    return new ExternalObject(setting1, setting2);
  }
}

I am trying to test this class and I have been able to mock out settings correctly. However I am unable to mock the external object construction new ExternalObject(setting1, setting2); and my test case is failing as it is trying to construct actual object (which fails as the parameters passed are not valid values in real).

describe("Create Instance", () => {  
  test("Allow creation of external instance", () => {
    // not sure if I am using this correctly? 
    // Aim is to mock out external module and any objects it may need creating
    jest.mock('external-library', () => {
      return {
        ExternalObject: jest.fn().mockImplementation()
      }
    });
    let settings: ISetting = new Settings();
    jest.spyOn(settings, "getSetting")
    .mockImplementationOnce(() => 'abcd')
    .mockImplementationOnce(() => 'xyz')

    let myImpl = new MyClass();
    let inst = myImpl.createInstance(settings);
    // expecting that the instance is created successfully.
    expect(inst).toBeTruthy();
  });
});

However I was not sure what I am doing wrong here. I did go through the documentation and some of the other questions but was unable to understand what I am missing out on.


Solution

  • Using jest.mock() in a function scope, the mocked module will not be hoisted to the top of the code block. This will result in the external-library module not being mocked when importing the code being tested. Just move jest.mock() to the module scope of the test file.

    E.g.

    MyClass.ts:

    //@ts-ignore
    import { ExternalObject } from 'external-library';
    import { ISettings } from './Settings';
    
    export class MyClass {
      public createInstance(settings: ISettings): ExternalObject {
        const setting1 = settings.getSetting('setting1');
        const setting2 = settings.getSetting('setting2');
        return new ExternalObject(setting1, setting2);
      }
    }
    

    Settings.ts:

    export interface ISettings {
      getSetting(key: string): void;
    }
    
    export class Settings implements ISettings {
      getSetting(key: string) {
        return 'real implementation';
      }
    }
    

    MyClass.test.ts:

    import { MyClass } from './MyClass';
    import { ISettings, Settings } from './Settings';
    //@ts-ignore
    import { ExternalObject } from 'external-library';
    
    jest.mock(
      'external-library',
      () => {
        return { ExternalObject: jest.fn() };
      },
      { virtual: true }
    );
    
    describe('68545423', () => {
      afterEach(() => {
        jest.restoreAllMocks();
      });
      afterAll(() => {
        jest.resetAllMocks();
      });
      test('should pass', () => {
        let myImpl = new MyClass();
        let settings: ISettings = new Settings();
        jest
          .spyOn(settings, 'getSetting')
          .mockImplementationOnce(() => 'abcd')
          .mockImplementationOnce(() => 'xyz');
    
        let inst = myImpl.createInstance(settings);
        expect(inst).toBeTruthy();
        expect(ExternalObject).toBeCalledWith('abcd', 'xyz');
      });
    });
    

    test result:

     PASS  examples/68545423/MyClass.test.ts (7.233 s)
      68545423
        ✓ should pass (4 ms)
    
    -------------|---------|----------|---------|---------|-------------------
    File         | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
    -------------|---------|----------|---------|---------|-------------------
    All files    |   85.71 |      100 |      50 |   85.71 |                   
     MyClass.ts  |     100 |      100 |     100 |     100 |                   
     Settings.ts |      50 |      100 |       0 |      50 | 7                 
    -------------|---------|----------|---------|---------|-------------------
    Test Suites: 1 passed, 1 total
    Tests:       1 passed, 1 total
    Snapshots:   0 total
    Time:        8.032 s, estimated 9 s
    Ran all test suites related to changed files.