最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Is it possible to stub an exported function in a CommonJS module using sinon? - Stack Overflow

programmeradmin5浏览0评论

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
Add a ment  | 

3 Answers 3

Reset to default 4

Addendum 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);
发布评论

评论列表(0)

  1. 暂无评论