How to stub exported function in ES6?
JavascriptEcmascript 6Javascript Problem Overview
I have file foo.js:
export function bar (m) {
console.log(m);
}
And another file that uses foo.js, cap.js:
import { bar } from 'foo';
export default m => {
// Some logic that I need to test
bar(m);
}
I have test.js:
import cap from 'cap'
describe('cap', () => {
it('should bar', () => {
cap('some');
});
});
Somehow I need override implementation of bar(m)
in test. Is there any way to do this?
P.S. I use babel, webpack and mocha.
Javascript Solutions
Solution 1 - Javascript
Ouch.. I found solution, so I use sinon
to stub and import * as foo from 'foo'
to get object with all exported functions so I can stub them.
import sinon from 'sinon';
import cap from 'cap';
import * as foo from 'foo';
sinon.stub(foo, 'bar', m => {
console.log('confirm', m);
});
describe('cap', () => {
it('should bar', () => {
cap('some');
});
});
Solution 2 - Javascript
You can replace/rewrite/stub exports only from within the module itself. (Here's an explanation)
If you rewrite 'foo.js' like this:
var bar = function bar (m) {
console.log(m);
};
export {bar}
export function stub($stub) {
bar = $stub;
}
You can then override it in your test like this:
import cap from 'cap'
import {stub} from 'foo'
describe('cap', () => {
it('should bar', () => {
stub(() => console.log('stubbed'));
cap('some'); // will output 'stubbed' in the console instead of 'some'
});
});
I've created a Babel plugin that transforms all the exports automatically so that they can be stubbed: https://github.com/asapach/babel-plugin-rewire-exports
Solution 3 - Javascript
While @Mike solution would work in old versions of sinon, it has been removed since sinon 3.0.0.
Now instead of:
sinon.stub(obj, "meth", fn);
you should do:
stub(obj, 'meth').callsFake(fn)
Example of mocking google oauth api:
import google from 'googleapis';
const oauth2Stub = sinon.stub();
sinon.stub(google, 'oauth2').callsFake(oauth2Stub);
oauth2Stub.withArgs('v2').returns({
tokeninfo: (accessToken, params, callback) => {
callback(null, { email: '[email protected]' }); // callback with expected result
}
});
Solution 4 - Javascript
You can use babel-plugin-rewire (npm install --save-dev babel-plugin-rewire
)
And then in test.js
use the __Rewire__
function on the imported module to replace the function in that module:
// test.js
import sinon from 'sinon'
import cap from 'cap'
describe('cap', () => {
it('should bar', () => {
const barStub = sinon.stub().returns(42);
cap.__Rewire__('bar', barStub); // <-- Magic happens here
cap('some');
expect(barStub.calledOnce).to.be.true;
});
});
Be sure to add rewire
to your babel plugins in .babelrc
:
// .babelrc
{
"presets": [
"es2015"
],
"plugins": [],
"env": {
"test": {
"plugins": [
"rewire"
]
}
}
}
Lastly, as you can see the babel-plugin-rewire
plugin is only enabled in the test environment, so you should call you test runner with the BABEL_ENV
environment variable set to test
(which you're probably doing already):
env BABEL_ENV=test mocha --compilers js:babel-core/register test-example.js
Note: I couldn't get babel-plugin-rewire-exports
to work.
Solution 5 - Javascript
This was definitely a gotcha for me too...
I created a little util to workaround this limitation of sinon. (Available in js too).
// mockable.ts 👇
import sinon from 'sinon'
export function mockable<T extends unknown[], Ret>(fn: (...fnArgs: T) => Ret) {
let mock: sinon.SinonStub<T, Ret> | undefined
const wrapper = (...args: T) => {
if (mock) return mock(...args)
return fn(...args)
}
const restore = () => {
mock = undefined
}
wrapper.mock = (customMock?: sinon.SinonStub<T, Ret>) => {
mock = customMock || sinon.stub()
return Object.assign(mock, { restore })
}
wrapper.restore = restore
return wrapper
}
If you paste the above snippet into your project you can use it like so
foo.js
import { mockable } from './mockable'
// we now need to wrap the function we wish to mock
export const foo = mockable((x) => {
console.log(x)
})
main.js
import { foo } from './foo'
export const main = () => {
foo('asdf') // use as normal
}
test.js
import { foo } from './foo'
import { main } from './main'
// mock the function - optionally pass in your own mock
const mock = foo.mock()
// test the function
main()
console.assert(mock.calledOnceWith('asdf'), 'not called')
// restore the function
stub.restore()
The benefit of this approach is that you don't have to remember to always import the function in a certain way. import { foo } from './foo'
works just as well as import * as foo from './foo'
. Automatic imports will likely just work in your IDE.