sinon spy on standalone function

giangnn picture giangnn · Sep 1, 2015 · Viewed 14.6k times · Source

When I use Sinon on a function inside an object, it works:

function myFunc() {
    console.log('hello');
}
var myObj = { myFunc: myFunc };
var spy = sinon.stub(myFunc);
myObj.myFunc();
expect(spy.called).to.be.true(); 

However, I don't know why when I use Sinon on a standalone function as follow:

function myFunc() {
    console.log('hello');
}
var spy = sinon.stub(myFunc);
myFunc();
expect(spy.called).to.be.true(); 

the assertion fails.

Answer

Igwe Kalu picture Igwe Kalu · Aug 23, 2017

You were kinda on the right path but deviated. Let's walk through your effort and get things right:

// ...
function myFunc() {
    console.log( 'hello' );
}
var spiedMyFunc = sinon.spy( myFunc ); // what you want is a 'spy' not a 'stub'
// ...

Then at this point spiedMyFunc wraps myFunc. Hence, calling spiedMyFunc() should mostly amount to the same outcome as calling myFunc(). Meanwhile spiedMyFunc additionally

records arguments, this value, exceptions and return values for all calls.

So the rest of the code snippet should go as follows:

// myFunc(); // no you should be calling spiedMyFunc() instead
spiedMyFunc();
expect( spiedMyFunc.called ).to.be.true();

And that is how you spy on a standalone function. However, it does not make conceptual sense to stub a standalone function.


Answer to @charlesdeb's question in a comment on this answer:

When a method is called, it can trigger a chain that implicitly calls other methods. Due to this implicit or indirect calls, you may want to control how other methods in the chain behave while studying the behaviour of a particular method. Stubbing is a means for realising the said control.

When working with functions it is beneficial to actually follow the functional paradigm to make things easy and reliable. Consider this:

function a () { ... }

function b () { a(); }

When testing b, it is necessary and sufficient to prove that the execution of b() in turn executed a(). But with the way b was implemented, it is impossible to verify that a was called.

However, if we applied the functional paradigm then we should have

function a () { ... }

function b ( a ) { a(); }

Consequently we can write a simple test as follows:

var a_spy = sinon.spy( a );

var actualOutcomeOfCallingB = b( a_spy );

expect( a_spy.called ).to.be.true;
expect( actualOutcomeOfCallingB ).to.equal( expectedOutcome );

If you'd like to stub a, then instead of creating a spy with the real a do so with your preferred stub.

var a_stub = sinon.spy( function () { /* behaves differently than the actual `a` */ }  ); // that's your stub right there!

var actualOutcomeOfCallingB = b( a_stub );

expect( a_stub.called ).to.be.true;
expect( actualOutcomeOfCallingB ).to.equal( expectedOutcome );