Many node modules export a single function (not a constructor function, but a general purpose "utility" function) as its "module.exports". Is it possible to use Sinon.js to stub this standalone function?
// some module, "sum.js" that's "required" throughout the application
module.exports = function(a, b) {
return a + b;
};
// test.js
var sum = require('sum');
...
beforeEach(function() {
sumStub = sinon.stub(sum);
// throws: TypeError: Attempted to wrap undefined property undefined as function
});
afterEach(function() {
sumStub.restore();
});
...
Is there any way to achieve this?
Unfortunately not.
For npm you can use https://github.com/thlorenz/proxyquire or similar. Michael Feathers would call this a link seam.
It's not elegant, but doable.
Once you have that in place, you can use Sinon as you normally would.
In the long run, you might want to move your architecture towards _object seams_, but it's a solution that works today. Similar projects exist for RequireJS.
function MyFunction(){}
module.exports = function(){ return module.exports.MyFunction.apply(this, arguments) }
module.exports.MyFunction = MyFunction
Then you can stub require('./MyFunction').MyFunction and the rest of your code will without change see the stubbed edition.
:dizzy: :dizzy_face: :dizzy:
Stumbled across the same thing the other day, here's what I did:
const proxyquire = require('proxyquire')
const sinon = require('sinon')
const sum = sinon.stub()
const ModuleWithDependency = proxyquire('module', {
'sum': sum
})
works pretty well for our case.
If using CommonJS:
const myStubbedModule = function( absoluteModulePath ) {
const stub = sinon.stub();
require.cache[ require.resolve( absoluteModulePath ) ] = stub;
return stub;
}
_Note_: Depending on whether you're transpiling you may need to do:
require.cache[ require.resolve( absoluteModulePath ) ] = {
default: stub,
exports: stub
}
Often during tests I'll need to be inserting one stub for one specific test. The wrapper-function approach I took lets me modify the codebase and insert my stubs whenever I want, without having to either take a stub-first approach or play whack-a-mole with modules having references to the other modules I'm trying to stub and replace-in-place.
I've had a number of code reviews where people have pushed me towards hacking at the Node module layer, via proxyquire, mock-require, &c, and it starts simple and seems less crufty, but becomes a very difficult challenge of getting the stubs needed into place during test setup. With proxyquire at least one can proxyquire() a micro-/fixture- sized version of the app, something top level, & all stubs will be brought in during it's load, but tackling this at a JS language level rather than Node module level continues to strike me as significantly more straightforward, and easier to manage consistently and without danger (caveat: so long as one remembers to restore).
I made this module to more easily stub modules https://github.com/caiogondim/stubbable-decorator.js
I was just playing with Sinon and found simple solution which seem to be working - just add 'arguments' as a second argument
const sumStub = sinon.stub(sum, 'arguments');
sumStub.withArgs(2, 2).returns(4);
sumStub.withArgs(3, 3).returns(6);
Try this
import * as sum from './sum'
sinon.stub(sum, 'default', () => {
// stubbed function
});
@harryi3t - thanks that worked for me.
@harryi3t That didn't work for me, using ES Modules.
Error: can't redefine non-configurable property "default"
@elliottregan ES Modules are not stubbable per the STANDARD. We even have tests covering this behaviour. You can still do it, though, as I discuss here.
A lot of people are not actually testing ES Modules, but transpiled ES Modules (using Webpack/Babel, etc). The resulting ES5 uses getters to emulate how ES Modules work. You might be doing that, but try the simple route I suggest in the linked thread. mocha --register ... gets you a long way.
Possible workaround
(function (defineProperty) {
Object.defineProperty = (obj, prop, desc) => {
desc.configurable = true;
return defineProperty(obj, prop, desc);
};
})(Object.defineProperty);
@Sujimoshi Workaround for what exactly? What's the context for your fix? Also, where would people put the fix? In its current incarnation, it's missing a bit too much info to be helpful. It wouldn't help the original question and won't work for ES Modules. I am guessing that it concerns code that has been processed by Webpack 4, as it might apply (depending on your toolchain) to code written using ES2015+ syntax which have been _transpiled_ into ES5, _emulating_ the immutability of ES Modules through non-configurable object descriptors.
Try this
import * as sum from './sum' sinon.stub(sum, 'default', () => { // stubbed function });
deprecated now.
I just rewrote my ES6 module from this:
export default const doCoolStuff = () => {
// do the cool stuff...
}
to this:
export default {
doCoolStuff: () => {
// do the cool stuff...
}
}
It's a bit clunky, but enabled me to wrap the function in a stub.
now is almost 2021, is it possible?
now is almost 2021, is it possible?
Not much different than 2019.
ES Modules haven't changed, CommonJS hasn't changed, JavaScript hasn't changed.
Most helpful comment
Try this