2

I'm having an issue with stubbing a particular function with sinon after having used proxyquire.

Example:

// a.js
const api = require('api');

module.exports = (function () {
    return {
        run,
        doStuff
    };

    function run() {
        return api()
            .then((data) => {
                return doStuff(data);
            })
    }

    function doStuff(data) {
        return `Got data: ${data}`;
    }
})()

// a.spec.js - in the test
a = proxyquire('./a', {
    'api': () => Promise.resolve('data')
})
sinon.stub(a, 'doStuff');
// RUN TEST - call a.run()

I know it isn't working because it calls the original doStuff instead of a mocked/stubbed doStuff.

kjonsson
  • 2,799
  • 2
  • 14
  • 23
  • It kind of depends what you're trying to test. Are you trying to test that `doStuff` is called? Are you trying to test internal logic inside `doStuff`? Are you trying to make sure a certain value is returned from `run`? – JamesENL Nov 29 '16 at 03:25
  • @JamesENL I'm testing run. I just want to check what doStuff is called with. – kjonsson Nov 29 '16 at 10:08
  • Possible duplicate of [How to spy a function with Sinon.js that is in the same js file as the function under test](http://stackoverflow.com/questions/40660832/how-to-spy-a-function-with-sinon-js-that-is-in-the-same-js-file-as-the-function) – rabbitco Nov 29 '16 at 11:32

1 Answers1

1

I know it isn't working because it calls the original doStuff instead of a mocked/stubbed doStuff.

That is because function run() in a.js invokes function doStuff(data) inside the closure function run() is holding over the content of a.js and not function doStuff(data) in a_spec.js.

Illustration by example

Lets re-write a.js to:

var a = 'I am exported';
var b = 'I am not exported';

function foo () {
    console.log(a);
    console.log(this.b)
}

module.exports.a=a;
module.exports.foo=foo;

and a.spec.js to:

var one = require('./one');

console.log(one); // { a: 'I am exported', foo: [Function: foo] }

one.a = 'Charles';
one.b = 'Diana';

console.log(one); // { a: 'Charles', foo: [Function: foo], b: 'Diana' }

Now if we invoke one.foo() it will result in:

I am exported
Diana

The I am exported is logged to the console because console.log(a) inside foo points to var a inside the closure foo is holding over the contents of a.js.

The Diana is logged to the console because console.log(this.b) inside foo points to one.b in a.spec.js.

So what do you need to do to make it work?

You need to change:

module.exports = (function () {
    return {
        run,
        doStuff
    };

    function run() {
        return api()
            .then((data) => {
                return doStuff(data);
            })
    }

    function doStuff(data) {
        return `Got data: ${data}`;
    }
})()

to:

module.exports = (function () {
    return {
        run,
        doStuff
    };

    function run() {
        return api()
            .then((data) => {
                return this.doStuff(data); // ´this.doStuff´ points to ´doStuff´ in the exported object
            }.bind(this)) // to ensure that ´this´ does not point to the global object
    }

    function doStuff(data) {
        return `Got data: ${data}`;
    }
})()
rabbitco
  • 2,790
  • 3
  • 16
  • 38