Search code examples
javascriptunit-testingsinon

How to stub constant functions when using ES Modules with sinon.js?


With ES Modules, we define some exported functions as const and it is often a standard practice. However, during unit testing, these const functions can't be stubbed out which seems problematic from a unit testing perspective. For example,

import * as fs from 'fs';

import { bindNodeCallback } from 'rxjs';
import { map } from 'rxjs/operators';

// readFile :: string, string => Observable<string>
export const readFile$ = bindNodeCallback(fs.readFile);

// readJson :: string => Observable<any>
export function readJson(fileName) {
    return readFile$(fileName, 'utf-8')
        .pipe(map((rawFile) => JSON.parse(rawFile)));
}

Now to unit test readJson, I would typically want to stub readFile$ function. Unfortunately, following Sinon code doesn't work:

// Setup data - stubs / mocks
const stub = sinon.stub(myFs, 'readFile$').returns(json$);

Since Sinon is simply changing reference myFs.readFile$, original const still points to the original function which is in turn called by readJson function.

Any suggestion - how can I really stub/mock constant function within the same module?


Solution

  • const is constant one can't change it using "normal" code. Unfortunately sinon is not magic. You need to instrument your code to allow changing constant value.

    Assuming you are using babel to transpile you could use babel-plugin-rewire.

    After adding it to your babel config you would be able to do the following injection in your test code

    import { default as readJson, __RewireAPI__ as rewire } from './path/to/readJson.js'
    
    const stub = sinon.stub(myFs, 'readFile$').returns(json$)
    rewire.__set__('readFile$', stub)
    
    // readJson() would now call provided stub