Search code examples
javascriptnode.jsunit-testingjestjssinon

Javascript mock third-party node library on function call


I'm using sinon and I want to do something very simple, and I cannot find any solution after a lot of searching.

I have class MessageService class looks like this:

import sb from "@azure/service-bus";

    export class MessageService {

        async handleNewMessage() {

            //some code ....

            await sb.delay(5000);

           //some code ....
        }

    }

I want to test the handleNewMessage method , but inside there is call to third party library sb.delay(5000) (that actually sleep for 5s)
I want to replace this function and make it to do nothing.

In my messageService.spec.js file I tried to import it and replace it, but it's not working.

import sb from "@azure/service-bus";

describe('message service', () => {

 beforeEach(() => {
        sinon.stub(sb, 'delay').returns(()=>{});
    });

 afterEach(() => {
        sinon.restore();
    });

it("handleNewMessage should work", async () => {

        //This is call to the real sb.delay function
        await messageServiceMock.handleNewMessage();

    });

})

Is there any way to replace the original class third party function call?


Solution

  • From this comment: https://github.com/sinonjs/sinon/issues/1711#issuecomment-369220753

    we're not allowed to modify modules that are imported using import. It looks like imports are read-only views on exports.

    So, sinon.stub is not able to modify the imported module.

    Take a look at sb.delay implementation:

    export function delay<T>(t: number, value?: T): Promise<T> {
      return new Promise(resolve => setTimeout(() => resolve(value), t));
    }
    //...
    export { delay }
    

    Here is the solution:

    MessageService.ts:

    import * as sb from "@azure/service-bus";
    
    export class MessageService {
      async handleNewMessage() {
        await sb.delay(5000);
      }
    }
    

    MessageService.test.ts:

    import sinon from "sinon";
    import proxyquire from "proxyquire";
    
    describe("message service", () => {
      afterEach(() => {
        sinon.restore();
      });
    
      it("handleNewMessage should work", async () => {
        const delayStub = sinon.stub().resolves({});
        const { MessageService } = proxyquire("./MessageService.ts", {
          "@azure/service-bus": {
            delay: delayStub,
          },
        });
        const messageServiceMock = new MessageService();
        await messageServiceMock.handleNewMessage();
        sinon.assert.calledWith(delayStub, 5000);
      });
    });
    

    Unit test result with 100% coverage:

     message service
        ✓ handleNewMessage should work (1343ms)
    
    
      1 passing (1s)
    
    ------------------------|----------|----------|----------|----------|-------------------|
    File                    |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
    ------------------------|----------|----------|----------|----------|-------------------|
    All files               |      100 |      100 |      100 |      100 |                   |
     MessageService.test.ts |      100 |      100 |      100 |      100 |                   |
     MessageService.ts      |      100 |      100 |      100 |      100 |                   |
    ------------------------|----------|----------|----------|----------|-------------------|
    

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