In almost 2021, is there a way to mock a single function? I mean function without object.
// demo.js
module.exports = () => {
return 'real func demo';
};
// demo.test.js
const demo = require("./demo");
// ...
sinon.stub(demo).callsFake(() => {
return 'mocked function';
});
expect(demo()).to.eq('mocked function')
In almost 2021, is there a way to mock a single function? I mean function without object.
// demo.js
module.exports = () => {
return 'real func demo';
};
// demo.test.js
const demo = require("./demo");
// ...
sinon.stub(demo).callsFake(() => {
return 'mocked function';
});
expect(demo()).to.eq('mocked function')
Share
Improve this question
edited Jul 8, 2021 at 10:23
oligofren
23.1k17 gold badges113 silver badges202 bronze badges
asked Dec 31, 2020 at 20:09
RollyRolly
3,3854 gold badges29 silver badges36 bronze badges
3
- 1 Does this answer your question: github./sinonjs/sinon/issues/562#issuement-753321689 – Lin Du Commented Jan 2, 2021 at 11:33
- 1 It's almost 2024. Would be cool if you could accept an answer :D – oligofren Commented Mar 28, 2023 at 17:55
- sinonjs/how-to/typescript-swc – oligofren Commented Aug 30, 2024 at 11:19
3 Answers
Reset to default 4Addendum in 2024: I wrote a real-world stubbing guide at sinon that might be useful for those who are inquiring into how one could achieve dependency stubbing with ESM and/or TypeScript. I list 3 very different approaches, from tooling to pure dependency injection, that all achieve this goal.
I'll just quote myself here:
You cannot stub standalone exported functions in an ES2015 pliant module (ESM) nor a CommonJS module in Node. That is just how these module systems work. It's only after transforming them into something else you might be able to achieve what you want. How you replace modules is totally environment specific and is why Sinon touts itself as "Standalone test spies, stubs and mocks for JavaScript" and not a module replacement tool, as that is better left to environment specific utils (proxyquire, various webpack loaders, Jest, etc) for whatever env you are in.
For a more deep-dive into why this will never work in CommonJS when using destructuring, see my answer here. You need to inject a module loader that intercepts the loading.
This is what trying to use Sinon to stub out an ESM module looks like in a spec pliant environment:
$ node esm-stubbing.mjs
/private/tmp/test/node_modules/sinon/lib/sinon/stub.js:72
throw new TypeError("ES Modules cannot be stubbed");
^
TypeError: ES Modules cannot be stubbed
at Function.stub (/private/tmp/test/node_modules/sinon/lib/sinon/stub.js:72:15)
at Sandbox.stub (/private/tmp/test/node_modules/sinon/lib/sinon/sandbox.js:388:37)
at file:///private/tmp/test/esm-stubbing.mjs:4:7
$ cat esm-stubbing.mjs
import sinon from "sinon";
import * as myModule from "./module.mjs";
sinon.stub(myModule, "foo").returns(42);
This is just Sinon itself throwing an error when it sees that the namespace of the module is read-only, instead of letting Node throw a more cryptic error message.
As @oligofren says, you need to intercept the loading of the module in your test script, because once that reference exists in your test there you can't change it. However, there is a package called rewire which allows you to intercept the normal import flow by creating a private getter and setter on the import, and thereby dynamically importing them for overriding.
Example from rewire:
// lib/myModule.js
var fs = require("fs"),
path = "/somewhere/on/the/disk";
function readSomethingFromFileSystem(cb) {
console.log("Reading from file system ...");
fs.readFile(path, "utf8", cb);
}
exports.readSomethingFromFileSystem = readSomethingFromFileSystem;
And in your test module:
// test/myModule.test.js
var rewire = require("rewire");
var myModule = rewire("../lib/myModule.js");
var fsMock = {
readFile: function (path, encoding, cb) {
expect(path).to.equal("/somewhere/on/the/disk");
cb(null, "Success!");
}
};
myModule.__set__("fs", fsMock);
myModule.readSomethingFromFileSystem(function (err, data) {
console.log(data); // = Success!
});
you can import with as
syntax.
if you exported as default
then use 'default' instead of the actual name
import * as yourModule from '../<path>';
const fake = sinon.fake.returns(<value>);
sinon.replace(yourModule, 'default', fake);