Search code examples
typescriptunit-testingmockingjestjstypegoose

Mock functions of an imported object in TypeScript


I have a TypeScript file which exports two objects (created by Typegoose).

// my-document.ts
export class MyDocument extends Typegoose {
  // ...
}

export const MyDocumentModel = new MyDocument().getModelForClass(MyDocument, ...)

I import the script into the script under test.

// script-under-test.ts
import { MyDocumentModel } from './my-document';

export async function createDocument(doc: any) {
  return await MyDocumentModel.create(doc);
}

Now I tried to mock the call MyDocumentModel.create(doc).

// script-under-test.test.ts
import { MyDocumentModel } from './my-document';
import { createDocument } from './script-under-test.ts';

jest.mock('./my-document');

describe('creation', () => {
  const MyDocumentModelMock = MyDocumentModel as unknown as jest.Mock;

  it('should create a new document', async () => {
    const payload = { foo: 'bar' };
    const document = {} as Document;
    const createMock = jest.fn();
    createMock.mockReturnValueOnce(document);
    MyDocumentModel.mockImplementation(() => ({ create: createMock }));

    const result = await createDocument(payload);

    expect(createMock).toHaveBeenCalledWith(payload);
    expect(result).toStrictEqual(document);
  });
});

But I got the following error - Number of calls: 0.

How can I mock a function of an imported object?


Solution

  • You should implementate the factory function of jest.mock(moduleName, factory, options).

    E.g.

    my-document.ts:

    // simulate the Typegoose class
    class Typegoose {
      public getModelForClass(cls) {
        return cls.toString();
      }
    }
    
    export class MyDocument extends Typegoose {}
    
    export const MyDocumentModel = new MyDocument().getModelForClass(MyDocument);
    

    script-under-test.ts:

    import { MyDocumentModel } from './my-document';
    
    export async function createDocument(doc: any) {
      return await MyDocumentModel.create(doc);
    }
    

    script-under-test.test.ts:

    import { MyDocumentModel } from './my-document';
    import { createDocument } from './script-under-test';
    
    jest.mock('./my-document', () => {
      const mMyDocumentModel = { create: jest.fn() };
      return { MyDocumentModel: mMyDocumentModel };
    });
    
    describe('creation', () => {
      it('should create a new document', async () => {
        const payload = { foo: 'bar' };
        MyDocumentModel.create.mockResolvedValueOnce(document);
        const result = await createDocument(payload);
        expect(MyDocumentModel.create).toHaveBeenCalledWith(payload);
        expect(result).toStrictEqual(document);
      });
    });
    

    unit test results with 100% coverage:

     PASS  stackoverflow/61388982/ script-under-test.test.ts (10.103s)
      creation
        ✓ should create a new document (5ms)
    
    ----------------------|---------|----------|---------|---------|-------------------
    File                  | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
    ----------------------|---------|----------|---------|---------|-------------------
    All files             |     100 |      100 |     100 |     100 |                   
     script-under-test.ts |     100 |      100 |     100 |     100 |                   
    ----------------------|---------|----------|---------|---------|-------------------
    Test Suites: 1 passed, 1 total
    Tests:       1 passed, 1 total
    Snapshots:   0 total
    Time:        11.535s