Search code examples
typescriptunit-testingexpressmockingsinon

How to mock class instantiated inside function - Sinon?


Let say I have a function like follow.

import NetworkService from './services';

async function sendAPIRequest(data: any){
  // validations
  const service = new NetworkService();
  await service.call(data)
}

my test looks like this which used mocha, chai, sinon.

describe('sendAPIRequest', function(){
   it('make api', async function(){

       // trying to mock Network service like below
       const serviceMock = sinon.createStubInstance(NetworkService);
       await sendAPIRequest({name: 'foobar'})
   });
});

but I am getting error like

Error: Expected to stub methods on object but found none

How to mock my NetworkService. when testing sendAPIRequest.


Solution

  • sinon.createStubInstance() API will not replace the original call method of the imported NetworkService with a stubbed one. It just creates a stub instance, so you need to pass this stub instance into your sendAPIRequest and use it. Which means you should use it as a dependency injection pattern.

    There are two ways to test your code:

    1. stub the call method in the NetworkService.prototype

    index.ts:

    import NetworkService from "./services";
    
    export async function sendAPIRequest(data: any) {
      const service = new NetworkService();
      await service.call(data);
    }
    

    services.ts:

    export default class NetworkService {
      public async call(data) {
        return "real implementation";
      }
    }
    

    index.test.ts:

    import sinon from "sinon";
    import NetworkService from "./services";
    import { sendAPIRequest } from "./";
    
    describe("sendAPIRequest", function() {
      afterEach(() => {
        sinon.restore();
      });
    
      it("should make api", async () => {
        const callStub = sinon.stub(NetworkService.prototype, "call");
        await sendAPIRequest({ name: "foobar" });
        sinon.assert.calledWithExactly(callStub, { name: "foobar" });
      });
    });
    

    Unit test results with coverage report:

     sendAPIRequest
        ✓ should make api
    
    
      1 passing (12ms)
    
    ---------------|----------|----------|----------|----------|-------------------|
    File           |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
    ---------------|----------|----------|----------|----------|-------------------|
    All files      |       95 |      100 |    83.33 |    94.44 |                   |
     index.test.ts |      100 |      100 |      100 |      100 |                   |
     index.ts      |      100 |      100 |      100 |      100 |                   |
     services.ts   |       75 |      100 |       50 |       75 |                 3 |
    ---------------|----------|----------|----------|----------|-------------------|
    
    1. use proxyquire module

    index.test.ts

    import proxyquire from "proxyquire";
    import sinon from "sinon";
    
    describe("sendAPIRequest", function() {
      afterEach(() => {
        sinon.restore();
      });
    
      it("make api", async function() {
        const networkServiceInstanceStub = {
          call: sinon.stub(),
        };
        const NetworkServiceStub = sinon.stub().callsFake(() => networkServiceInstanceStub);
        const { sendAPIRequest } = proxyquire("./", {
          "./services": {
            default: NetworkServiceStub,
          },
        });
        await sendAPIRequest({ name: "foobar" });
        sinon.assert.calledOnce(NetworkServiceStub);
        sinon.assert.calledWithExactly(networkServiceInstanceStub.call, { name: "foobar" });
      });
    });
    

    Unit test results with coverage report:

      sendAPIRequest
        ✓ make api (279ms)
    
    
      1 passing (286ms)
    
    ---------------|----------|----------|----------|----------|-------------------|
    File           |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
    ---------------|----------|----------|----------|----------|-------------------|
    All files      |    95.24 |      100 |    85.71 |       95 |                   |
     index.test.ts |      100 |      100 |      100 |      100 |                   |
     index.ts      |      100 |      100 |      100 |      100 |                   |
     services.ts   |       75 |      100 |       50 |       75 |                 3 |
    ---------------|----------|----------|----------|----------|-------------------|
    

    Related post:

    Source code: https://github.com/mrdulin/mocha-chai-sinon-codelab/tree/master/src/stackoverflow/59897060