Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Possible to stub a standalone utility function? #562

Closed
ghost opened this issue Sep 12, 2014 · 22 comments
Closed

Possible to stub a standalone utility function? #562

ghost opened this issue Sep 12, 2014 · 22 comments

Comments

@ghost
Copy link

@ghost ghost commented Sep 12, 2014

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?

@cjohansen
Copy link
Contributor

@cjohansen cjohansen commented Sep 12, 2014

Unfortunately not.

@cjohansen cjohansen closed this Sep 12, 2014
@mroderick
Copy link
Member

@mroderick mroderick commented Sep 12, 2014

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.

@rektide
Copy link

@rektide rektide commented Mar 13, 2015

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.

💫 😵 💫

@rmehner
Copy link

@rmehner rmehner commented Jun 6, 2015

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.

@glortho
Copy link

@glortho glortho commented Dec 14, 2015

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
}

@rektide
Copy link

@rektide rektide commented May 16, 2016

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).

@caiogondim
Copy link

@caiogondim caiogondim commented Dec 1, 2016

I made this module to more easily stub modules https://github.com/caiogondim/stubbable-decorator.js

@sinonjs sinonjs deleted a comment from eoaiu Sep 15, 2017
@Grzegorzsa
Copy link

@Grzegorzsa Grzegorzsa commented Jun 1, 2018

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);

@harryi3t
Copy link

@harryi3t harryi3t commented Jun 21, 2018

Try this

import * as sum from './sum'
sinon.stub(sum, 'default', () => {
  // stubbed function
});

@jacquesdev
Copy link

@jacquesdev jacquesdev commented Jan 30, 2019

@harryi3t - thanks that worked for me.

@elliottregan
Copy link

@elliottregan elliottregan commented Apr 15, 2019

@harryi3t That didn't work for me, using ES Modules.
Error: can't redefine non-configurable property "default"

@fatso83
Copy link
Contributor

@fatso83 fatso83 commented Apr 16, 2019

@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.

@Sujimoshi
Copy link

@Sujimoshi Sujimoshi commented Jun 27, 2019

Possible workaround

(function (defineProperty) {
    Object.defineProperty = (obj, prop, desc) => {
        desc.configurable = true;
        return defineProperty(obj, prop, desc);
    };
})(Object.defineProperty);

@fatso83
Copy link
Contributor

@fatso83 fatso83 commented Jun 27, 2019

@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.

@20-07
Copy link

@20-07 20-07 commented Feb 13, 2020

Try this

import * as sum from './sum'
sinon.stub(sum, 'default', () => {
  // stubbed function
});

deprecated now.

natalynyu added a commit to seas-computing/course-planner that referenced this issue Jun 26, 2020
…etCurrentUser due to version change

It turns out that you can no longer stub free-standing functions in Typescript 3.9.2+ according to this issue:

microsoft/TypeScript#38568
sinonjs/sinon#562

The test in App.test.tsx was failing when it tried to get the callCount for a stub for a free-standing function getCurrentUser, and currently, my package-lock.json has 3.9.5, so that appears to be why. My fix was to use an object where the functions become methods instead.
natalynyu added a commit to seas-computing/course-planner that referenced this issue Jun 26, 2020
… we can stub them as part of the TypeScript 3.9.2+ update

It turns out that you can no longer stub free-standing functions in Typescript 3.9.2+ according to this issue:

microsoft/TypeScript#38568
sinonjs/sinon#562

The test in App.test.tsx was failing when it tried to get the callCount for a stub for a free-standing function getCurrentUser, and currently, my package-lock.json has 3.9.5, so that appears to be why. My fix was to use an object where the functions become methods instead.
natalynyu added a commit to seas-computing/course-planner that referenced this issue Jun 26, 2020
It turns out that you can no longer stub free-standing functions in Typescript 3.9.2+ according to this issue:

microsoft/TypeScript#38568
sinonjs/sinon#562

Currently, my package-lock.json has 3.9.5, so that appears to be why I was having these problems in my test. My fix was to use an object where the functions become methods instead.
natalynyu added a commit to seas-computing/course-planner that referenced this issue Jun 26, 2020
It turns out that you can no longer stub free-standing functions in Typescript 3.9.2+ according to this issue:

microsoft/TypeScript#38568
sinonjs/sinon#562

The solution is to make an object, FacultyAPI, that contains those functions and export the object instead of the functions themselves
natalynyu added a commit to seas-computing/course-planner that referenced this issue Jun 26, 2020
…SchedulesForYear

It turns out that you can no longer stub free-standing functions in Typescript 3.9.2+ according to this issue:

microsoft/TypeScript#38568
sinonjs/sinon#562
natalynyu added a commit to seas-computing/course-planner that referenced this issue Aug 4, 2020
…etCurrentUser due to version change

It turns out that you can no longer stub free-standing functions in Typescript 3.9.2+ according to this issue:

microsoft/TypeScript#38568
sinonjs/sinon#562

The test in App.test.tsx was failing when it tried to get the callCount for a stub for a free-standing function getCurrentUser, and currently, my package-lock.json has 3.9.5, so that appears to be why. My fix was to use an object where the functions become methods instead.
natalynyu added a commit to seas-computing/course-planner that referenced this issue Aug 4, 2020
… we can stub them as part of the TypeScript 3.9.2+ update

It turns out that you can no longer stub free-standing functions in Typescript 3.9.2+ according to this issue:

microsoft/TypeScript#38568
sinonjs/sinon#562

The test in App.test.tsx was failing when it tried to get the callCount for a stub for a free-standing function getCurrentUser, and currently, my package-lock.json has 3.9.5, so that appears to be why. My fix was to use an object where the functions become methods instead.
natalynyu added a commit to seas-computing/course-planner that referenced this issue Aug 4, 2020
It turns out that you can no longer stub free-standing functions in Typescript 3.9.2+ according to this issue:

microsoft/TypeScript#38568
sinonjs/sinon#562

Currently, my package-lock.json has 3.9.5, so that appears to be why I was having these problems in my test. My fix was to use an object where the functions become methods instead.
natalynyu added a commit to seas-computing/course-planner that referenced this issue Aug 4, 2020
It turns out that you can no longer stub free-standing functions in Typescript 3.9.2+ according to this issue:

microsoft/TypeScript#38568
sinonjs/sinon#562

The solution is to make an object, FacultyAPI, that contains those functions and export the object instead of the functions themselves
natalynyu added a commit to seas-computing/course-planner that referenced this issue Aug 4, 2020
…SchedulesForYear

It turns out that you can no longer stub free-standing functions in Typescript 3.9.2+ according to this issue:

microsoft/TypeScript#38568
sinonjs/sinon#562
natalynyu added a commit to seas-computing/course-planner that referenced this issue Aug 4, 2020
It turns out that you can no longer stub free-standing functions in Typescript 3.9.2+ according to this issue:

microsoft/TypeScript#38568
sinonjs/sinon#562
@jaucourt
Copy link

@jaucourt jaucourt commented Aug 13, 2020

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.

natalynyu added a commit to seas-computing/course-planner that referenced this issue Sep 12, 2020
…etCurrentUser due to version change

It turns out that you can no longer stub free-standing functions in Typescript 3.9.2+ according to this issue:

microsoft/TypeScript#38568
sinonjs/sinon#562

The test in App.test.tsx was failing when it tried to get the callCount for a stub for a free-standing function getCurrentUser, and currently, my package-lock.json has 3.9.5, so that appears to be why. My fix was to use an object where the functions become methods instead.
natalynyu added a commit to seas-computing/course-planner that referenced this issue Sep 12, 2020
… we can stub them as part of the TypeScript 3.9.2+ update

It turns out that you can no longer stub free-standing functions in Typescript 3.9.2+ according to this issue:

microsoft/TypeScript#38568
sinonjs/sinon#562

The test in App.test.tsx was failing when it tried to get the callCount for a stub for a free-standing function getCurrentUser, and currently, my package-lock.json has 3.9.5, so that appears to be why. My fix was to use an object where the functions become methods instead.
natalynyu added a commit to seas-computing/course-planner that referenced this issue Sep 12, 2020
It turns out that you can no longer stub free-standing functions in Typescript 3.9.2+ according to this issue:

microsoft/TypeScript#38568
sinonjs/sinon#562

Currently, my package-lock.json has 3.9.5, so that appears to be why I was having these problems in my test. My fix was to use an object where the functions become methods instead.
natalynyu added a commit to seas-computing/course-planner that referenced this issue Sep 12, 2020
It turns out that you can no longer stub free-standing functions in Typescript 3.9.2+ according to this issue:

microsoft/TypeScript#38568
sinonjs/sinon#562

The solution is to make an object, FacultyAPI, that contains those functions and export the object instead of the functions themselves
natalynyu added a commit to seas-computing/course-planner that referenced this issue Sep 12, 2020
…SchedulesForYear

It turns out that you can no longer stub free-standing functions in Typescript 3.9.2+ according to this issue:

microsoft/TypeScript#38568
sinonjs/sinon#562
natalynyu added a commit to seas-computing/course-planner that referenced this issue Sep 12, 2020
It turns out that you can no longer stub free-standing functions in Typescript 3.9.2+ according to this issue:

microsoft/TypeScript#38568
sinonjs/sinon#562
natalynyu added a commit to seas-computing/course-planner that referenced this issue Sep 12, 2020
It turns out that you can no longer stub free-standing functions in Typescript 3.9.2+ according to this issue:

microsoft/TypeScript#38568
sinonjs/sinon#562

Export Multi Year Plan function from the API so that it is no longer a free-standing function and can be used appropriately in the test and in the multi year plan code.
natalynyu added a commit to seas-computing/course-planner that referenced this issue Sep 14, 2020
getMetadata cannot be exported as a standalone function. It turns out that you can no longer stub free-standing functions in Typescript 3.9.2+ according to this issue:

microsoft/TypeScript#38568
sinonjs/sinon#562
@rollrodrig
Copy link

@rollrodrig rollrodrig commented Dec 31, 2020

now is almost 2021, is it possible?

@mroderick
Copy link
Member

@mroderick mroderick commented Jan 1, 2021

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.

@MarceloBD
Copy link

@MarceloBD MarceloBD commented Jun 14, 2021

You can do if you write

import * as yourModule from ..

sinon.stub(yourModule, 'foo').returns(stuff)

ps: this should be done before calling the original method or class. Eg: if you are using chai-http for integration tests you should call this stub before instanciating server

@fatso83
Copy link
Contributor

@fatso83 fatso83 commented Jun 15, 2021

@MarceloBD That is not true for a spec compliant ES2015+ environment and is not true for CommonJS. In fact, we explicitly detect and test for this case to give a good error message saying what is happening when it does not work:
https://github.com/sinonjs/sinon/blob/master/test/es2015/module-support-assessment-test.es6#L53-L58

Your tip might be true if you utilize something that is not a spec compliant ESM environment, which is the case for some bundlers or if running using the excellent esm package (i.e. node -r esm main.js) with the CommonJS option mutableNamespace: true.

In any case, this issue from 2014 is really about CommonJS modules 😄

For the cases where your tip does apply, adding a small hint to the casual reader on what environment you are in (like "Webpack 5 + TypeScript 3.7 + Babel 10"/ link to config) will probably be useful for a lot of people 😻 .

@SSTPIERRE2
Copy link

@SSTPIERRE2 SSTPIERRE2 commented Jun 17, 2021

@MarceloBD 's solution works for me. We are using babel. Being able to stub a standalone function is a necessary feature for testing some functions. Not all functions are part of a class instance.

@fatso83
Copy link
Contributor

@fatso83 fatso83 commented Jun 17, 2021

Not all functions are part of a class instance.

And that's a good thing! Classes are hardly ever the right tool and is mostly just used as a crutch for people coming to JS from Java and C# land to make them feel more at home in the weird land of functions :trollface:

This still holds, though, @SSTPIERRE2 : you cannot stub standalone exported functions in a ES2015 compliant 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 module replacement tool, as that is better left to environment specific utils (proxyquire, various webpack loaders, Jest, etc) for whatever env you are in.

works for me. We are using babel.

And then you are probably no longer working with ES Modules, just something that looks like it. This is what Marcelo's suggestion looks like in Node:

$ 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);

which is just a friendly shield for what Node would otherwise tell you:

$ pbpaste > overwrite.mjs
import * as myModule from "./module.mjs";

myModule.foo = function overWrite() {};

$ node overwrite.mjs
file:///private/tmp/test/overwrite.mjs:3
myModule.foo = function overWrite() {};
             ^

TypeError: Cannot assign to read only property 'foo' of object '[object Module]'

It cannot be done per spec.

@sinonjs sinonjs deleted a comment from penguoir Jul 22, 2021
@sinonjs sinonjs locked and limited conversation to collaborators Jul 22, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet