I have just changed my lodash import from import _ from 'lodash';
to import debounce from 'lodash/debounce';
In my test I used to have sandbox.stub(_, 'debounce').returnsArg(0);
, but now I'm stuck as to what to change it to. Obviously sandbox.stub(debounce).returnsArg(0);
won't work. Not sure what to do when only a single function is exported from a module.
I have just changed my lodash import from import _ from 'lodash';
to import debounce from 'lodash/debounce';
In my test I used to have sandbox.stub(_, 'debounce').returnsArg(0);
, but now I'm stuck as to what to change it to. Obviously sandbox.stub(debounce).returnsArg(0);
won't work. Not sure what to do when only a single function is exported from a module.
- Does this answer your question? How to stub exported function in ES6? – david_adler Commented Jul 26, 2021 at 8:44
2 Answers
Reset to default 13This syntax:
import something from 'myModule';
...is ES6 syntax which binds something
to the default
export of 'myModule
'.
If the module is an ES6 module then you can stub the default
export of the module like this:
import * as myModule from 'myModule';
const sinon = require('sinon');
// ...
const stub = sinon.stub(myModule, 'default');
...but this only works if 'myModule'
is an ES6 module.
In this case 'lodash/debounce'
is not an ES6 module, it is shipped pre-piled. The last line is this:
module.exports = debounce;
...which means the module export is the debounce function.
This means that in order to stub 'lodash/debounce'
you have to mock the entire module.
Sinon doesn't provide module-level mocking so you will need to use something like proxyquire
:
const proxyquire = require('proxyquire');
const sinon = require('sinon');
const debounceStub = sinon.stub().returnsArg(0);
const code = proxyquire('[path to your code]', { 'lodash/debounce': debounceStub })
...or if you are using Jest
you can use something like jest.mock
:
jest.mock('lodash/debounce', () =>
jest.fn((func, ms) => func) // <= mock debounce to simply return the function
);
Details
The reason that stubbing the default
export of a module only works if it is an ES6 module is because of what happens during pilation.
ES6 syntax gets piled into pre-ES6 JavaScript. For example, Babel turns this:
import something from 'myModule';
...into this:
var _myModule = _interopRequireDefault(require("myModule"));
function _interopRequireDefault(obj) {
return obj && obj.__esModule ?
obj : // <= return the result of require("myModule") if it is an ES6 module...
{ default: obj }; // <= otherwise set it to the default property of a wrapper object
}
...so if 'myModule'
is an ES6 module it gets returned directly...but if it is not then the interop returns a wrapping object.
Since each import
gets a different wrapping object, changing the default
property on one doesn't affect the default
property of any others.
you can make yourself a wrapping file, which will eventually export the same lodash/debounce
instance for you, but this time you could stub that, e.g.:
myutils/lodash/debounce.js
import lodashDebounce from 'lodash/debounce';
const exports = {
debounce: lodashDebounce,
};
export const debounce = () => exports.debounce();
export default exports;
now, in your actual code import the debounce
not from the original location, but from this wrapping file instead:
BEFORE:
import debounce from 'lodash/debounce' // this is how we usually do
AFTER:
import { debounce } from 'myutils/lodash/debounce' // if we want to stub it
// all other code-lines remain the same
const method = () => {
debounce(callback, 150));
...
}
now when doing a test.js:
import lodashWrapped from 'myutils/lodash/debounce';
sinon.stub(lodashWrapped , 'debounce').callsFake((callbackFn) => {
// this is stubbed now
});
// go on, make your tests now