Stubbing method in same file using Sinon
The method mentioned above (using a factory
to collect the functions) works well; however, eslint will not like the use of a variable/function that has not yet been declared. Therefore I would recommend a slight modification:
// my-functions.js
export const factory = {};
export const funcA = () => {
return facory.funcB();
};
factory.funcA = funcA;
export const funcB = () => true;
factory.funcB = funcB;
// my-functions-test.js
import {factory, funcA, funcB} from './path/to/my-functions';
describe('MyFunctions | funcA', () => {
test('returns result from funcB call', () => {
const funcBStub = sinon.stub(factory, 'funcB').returns(false);
// Test that the function does not throw errors
let result;
expect(() => (result = funcA())).not.toThrow();
// Test that the return value is that of the mock rather than the original function
expect(result).toEqual(false);
// Test that the stub was called
expect(funcBStub.called).toEqual(true);
});
});
// Don't forget to test funcB independently ;)
The important distinction is to add the functions within the file to the factory
as they are defined to avoid break eslint rules. The only case where this could cause issues is if you tried calling one of those functions within the same file before they have all been defined. Example:
// my-functions-1.js
export const factory = {};
export const funcA = () => {
factory.funcB();
};
factory.funcA = funcA;
// Since the code execution runs from top to bottom, calling funcA here means that funcB has not yet been added to factory
funcA(); // Throws an error since factory.funcB() is not a function (yet)
export const funcB = () => true;
factory.funcB = funcB;
I prefer this technique of using a "collector" to call functions within the same file since it is not always a great idea to create separate files for EACH function that you write. Often, I find that I will create many related utility functions in order to make my code more readable, reusable, and composable; putting each function into a separate file would make the code slightly more difficult to understand since a reader could not see the definitions of these functions without bouncing between different files.
While the above does work, it's definitely a workaround as my linter was quick to inform.
I ended up separating modules and using proxyquire. This library allows you to easily substitute any / all exports with those of your choosing, sinon stub spy or mocks included. e.g. :
in b.js
export const fnB = () => 'hey there!';
in a.js
import { fbB } from 'b.js';
export const fnA = () => fbB();
in a.test.js
import { noCallThru } from 'proxyquire';
const proxyquireStrict = noCallThru();
const stubB = stub().returns('forced result');
const moduleA = proxyquireStrict('a.js', {
'b.js' : { fnB: stubB }
}).fnA;
console.log(fnA()); // 'forced result'
Some restructuring can make this work.
I've used commonJS syntax. Should work in the same way in ES6 as well.
foo.js
const factory = {
a,
b,
}
function a() {
return 2;
}
function b() {
return factory.a();
}
module.exports = factory;
test.js
const ser = require('./foo');
const sinon = require('sinon');
const aStub = sinon.stub(ser, 'a').returns('mocked return');
console.log(ser.b());
console.log(aStub.callCount);
Output
mocked return
1