Search code examples
javascriptnode.jstypescriptsinon

How to stub a module function that is called from another function


My module.ts file has 2 functions:

export async function foo() {
    var barVal = await bar();
    doSomethingWithBarVal(barVal);
}

export async function bar(): Bar {
    return await somethingAsync();
}

In my tests I want to stub bar() and return a mock for Bar (the returned value of bar())

My current test looks like this:

var module = require('module.ts');
var myStub = sinon.stub(module, 'bar').resolves(myMock);
await foo();
expect(myStub.calledOnce);

However, the expect always fails and the 'real' bar() is called.
If I call bar() directly from my test, then the stub is called but I want to test the whole flow.


Solution

  • The problem with your approach is that you are stubbing module object (exports since you are using commonjs require in your test) while your foo function uses bar available in module scope.

    To fix this you have a few options here.

    1. Quick and dirty

    Use exports.bar instead of bar in foo

    export async function foo() {
        var barVal = await exports.bar();
        doSomethingWithBarVal(barVal);
    }
    

    This approach is actually a hack that uses the fact that your module will be transpiled to commonjs format. Which might be not true someday but kinda works for now.

    2. Embrace static nature of ES modules

    Embrace the fact that ES modules are static. Make your function "testable again" by explicitly allowing to specify bar as its argument

    export async function foo(_bar = bar) {
        var barVal = await _bar();
        doSomethingWithBarVal(barVal);
    }
    
    // test.js
    await foo(mockBarImplementation)
    

    3. Advanced

    Use some IoC/DI implementation. For example typescript-ioc