19

I tried a dummy module and to stub it, but does not work.

the app.js

function foo()
{
    return run_func()
}
function run_func()
{
    return '1'
}
exports._test = {foo: foo, run_func: run_func}

the test.js

app = require("./app.js")._test
describe('test', function(){
    it('test', function(){

        var test_stub = sinon.stub(app, 'run_func').callsFake(
          function(){
            return '0'
        })
        test_stub.restore()

        var res = app.foo()
        assert.equal('0', res)
    })
})

I tried the advice from: sinon stub not replacing function

But still the same. It does not replace the function.

J.R.
  • 769
  • 2
  • 10
  • 25

1 Answers1

29

You have a couple of problems here. The first is that you're calling test_stub.restore() immediately after creating the stub, which causes it to replace itself with the original function, effectively undoing the stub completely.

restore is meant for cleaning up fake methods after your test is done. So you do want to call it, but you should do so in an afterEach.

Your second problem is a little more subtle. Sinon works by overwriting a reference to a function on an object, making it point at something else (in this case, the stub). It can't replace references to the same function in other contexts.

When you call sinon.stub(app, 'run_func'), it's a bit like this:

app.run_func = sinon.stub()

... except that the former way stores the original value and name of app.run_func, allowing you to easily restore it later.

Note that at this point, the variable app points to the same object you exported with exports._test = {foo: foo, run_func: run_func} Your foo function, however, is not referencing run_func through this object. It's referencing it directly in the scope of app.js, which sinon cannot affect.

Take a look at the following example. You'll also note I cleaned up a few other things:

app.js:

exports.foo = function() {
    return exports.run_func();
};

exports.run_func = function() {
    return '1';
};

test.js:

const app = require('./app');
const sinon = require('sinon');

describe('app', function() {
    describe('foo', function() {
        beforeEach(function() {
            sinon.stub(app, 'run_func').returns('0');
        });

        afterEach(function() {
            app.run_func.restore();
        });

        it('returns result of app.run_func', function() {
            assert.equal(app.foo(), '0');
        });
    });
});

Note how exports in app.js refers to the exact same object that app does in test.js. This is because modules in node export an empty object by default, which you can assign onto by way of the exports variable.

sripberger
  • 1,682
  • 1
  • 10
  • 21
  • 1
    Thnx! I am a newbee to JavaScript, so it seems that exports._test = {foo: foo, run_func: run_func} creates a new object that contains copies of the original function object( I don't know if it's correct to say this way, I am from c++), I thought it references to the original object. I have to directly export the function object rather than a copy? – J.R. Feb 23 '18 at 07:55
  • From a C/C++ perspective, it helps to think of every variable or object property in javascript as a pointer. You're not copying the functions to the new object so much as giving it references so you can access the functions through it. `exports` is the object that is returned by default when you `require` the module. It is an empty object by default, and you can assign function references onto it to "export" them. This is a pretty decent tutorial: https://www.sitepoint.com/understanding-module-exports-exports-node-js/ – sripberger Feb 23 '18 at 16:43
  • 1
    Is there a way to replace the function , no matter how its called? – Ashok kumar Mar 22 '20 at 16:14
  • @Ashokkumar Unfortunately, no. This limitation is built into the way JavaScript works. Within a CommonJS module, variables at the top level of that module are local to the module, and cannot be changed from outside of the module. So you need to refer to replaceable stuff through an object that will be accessible outside of the module. The default exports object is the best candidate for that, most of the time. – sripberger Mar 26 '20 at 14:30
  • Since this word is not to be found on this page yet: What we are looking here at, is indeed **a closure**, a reference (not to a literal value but a function, which as we know, functions are normal variables. Firmly „baked in“, thus unremovable. – Frank N Feb 01 '23 at 14:12
  • nb: I just worked through that eniture ES6 stubbing issue [on this SO post](https://stackoverflow.com/a/75311478/444255) – Frank N Feb 01 '23 at 14:13