Search code examples
typescriptunit-testingjestjsts-jest

ts-jest: Mock internal module


I have a following .ts module

import client from './client';

export default class DefaultRequest implements IRequest {
    make(req: Request): Promise<Response> {
        return new Promise<Response>((resolve, reject) => {
            client.post(req, (error: Error | null, res: Response) => {
                if (error) {
                    return reject(error);
                } else {
                    return resolve(res);
                }
            });
        });
    }
}

I am trying to write a unit test for this class with ts-jest, so that client is mocked (and returns some valid Response).

Here is how I am doing it:

import {mocked} from 'ts-jest/utils';
import client from './client';
import DefaultRequest from './request'

const mockedClient = mocked(client, true);
const testRequest = new DefaultRequest();

jest.mock('./client', () => {
  return {
    RestClient: jest.fn().mockImplementation(() => {
      return {
        post: () => {return someValidResponse();},
      };
    })
  };
});

describe('My Tests', () => {

    it('Unit Test 1', async () => {
        let res: Response = await testRequest.make(buildReq());
    });
});

But the mockedClient is still not mocked. This is what ./client.ts looks like:

import { RestClient } from '@rest/client';

export default new RestClient();

Can the internal client module that class DefaultRequest uses be mocked in this manner?

EDIT: I also tried jest.spyOn

const spiedMethod= jest.spyOn(client, 'post');
        const call: Request = new Request();
        const response: Response = await call.make(buildRequest());

        expect(spiedReleaseMethod).toHaveBeenCalled();
        expect(response.getResponsecode()).toBe(200);

And it still calls the original method and not the spied method.


Solution

  • You are testing the request.ts module that depends on the client.ts module. So you need to mock the client.ts module and its post method rather than the @rest/client package.

    E.g.

    request.ts:

    import client from './client';
    
    interface IRequest {
      make(req: Request): Promise<Response>;
    }
    
    export default class DefaultRequest implements IRequest {
      make(req: Request): Promise<Response> {
        return new Promise<Response>((resolve, reject) => {
          client.post(req, (error: Error | null, res: Response) => {
            if (error) {
              return reject(error);
            } else {
              return resolve(res);
            }
          });
        });
      }
    }
    

    client.ts: (No matter what package implementation client uses, as long as the interfaces exposed by the module are the same)

    export default {
      post(req, callback) {
        console.log('real implementation');
      },
    };
    

    request.test.ts:

    import { mocked } from 'ts-jest/utils';
    import client from './client';
    import DefaultRequest from './request';
    
    jest.mock('./client');
    
    const mockedClient = mocked(client);
    
    describe('68115300', () => {
      afterAll(() => {
        jest.resetAllMocks();
      });
      it('should pass', () => {
        mockedClient.post.mockImplementationOnce((req, callback) => {
          callback(null, 'mocked response');
        });
        const testRequest = new DefaultRequest();
        testRequest.make(('req' as unknown) as Request);
        expect(mockedClient.post).toBeCalledWith('req', expect.any(Function));
      });
    });
    

    test result:

     PASS  examples/68115300/request.test.ts (12.448 s)
      68115300
        ✓ should pass (3 ms)
    
    ------------|---------|----------|---------|---------|-------------------
    File        | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
    ------------|---------|----------|---------|---------|-------------------
    All files   |   77.78 |       50 |      75 |   77.78 |                   
     client.ts  |      50 |      100 |       0 |      50 | 3                 
     request.ts |   85.71 |       50 |     100 |   85.71 | 12                
    ------------|---------|----------|---------|---------|-------------------
    Test Suites: 1 passed, 1 total
    Tests:       1 passed, 1 total
    Snapshots:   0 total
    Time:        14.61 s