Last week, when I was going through test suite of the project I’m working on, I stumbled into this code:
const spy = sinon.spy();
const stub = sinon.stub();
Seeing these lines was new and confusing for me, so I’ve decided to dig deep and figure out what all of this means.
Sinon
Turns out that Sinon is a JavaScript library that provides developer with 3 specific testing tools: spies, stubs and mocks (aren’t considered here).
Writing an application we’re most likely have a few dependencies: it could be sending network requests to servers or connecting to databases. But when unit testing we don’t want all those requests to go out to actual servers or databases, so we either need to set up special testing environment or do it Sinon’s way and fake the request and response with special functions, tricking the system into thinking the request was made. In this case our testing isn’t affected by anything outside the test and can’t fail randomly.
Sinon allows us to replace these difficult and unreliable parts of tests with something that makes testing easier.
Spies
Spies are functions that offer information about function calls, without affecting their behavior.
We can create anonymous spies as above by calling sinon.spy
with no parameters:
const spy = sinon.spy();
or more common pattern is to replace another function with a spy:
const spy = sinon.spy(myFunc);
const spy = sinon.spy(object, "method");
The function sinon.spy
returns a Spy
object, which can be called like a function, but also contains properties with information on any calls made to it. Here is the list of properties for a Spy
object that can be accessed:
- alwaysCalledOn:ƒ ()
- alwaysCalledWith:ƒ ()
- alwaysCalledWithExactly:ƒ ()
- alwaysCalledWithMatch:ƒ ()
- alwaysCalledWithNew:ƒ ()
- alwaysReturned:ƒ ()
- alwaysThrew:ƒ ()
- args:[]
- callArg:ƒ ()
- callArgOn:ƒ ()
- callArgOnWith:ƒ ()
- callArgWith:ƒ ()
- callCount:0
- callIds:[]
- called:false
- calledAfter:ƒ calledAfter(spyFn)
- calledBefore:ƒ calledBefore(spyFn)
- calledImmediatelyAfter:ƒ calledImmediatelyAfter(spyFn)
- calledImmediatelyBefore:ƒ calledImmediatelyBefore(spyFn)
- calledOn:ƒ ()
- calledOnce:false
- calledThrice:false
- calledTwice:false
- calledWith:ƒ ()
- calledWithExactly:ƒ ()
- calledWithMatch:ƒ ()
- calledWithNew:ƒ ()
- displayName:“spy“
- errorsWithCallStack:[]
- exceptions:[]
- firstCall:null
- formatters:{c: ƒ, n: ƒ, D: ƒ, C: ƒ, t: ƒ, …}
- getCall:ƒ getCall(i)
- getCalls:ƒ ()
- id:“spy#0“
- instantiateFake:ƒ create(func, spyLength)
- invoke:ƒ invoke(func, thisValue, args)
- invokeCallback:ƒ ()
- isSinonProxy:true
- lastCall:null
- matches:ƒ (args, strict)
- matchingFakes:ƒ (args, strict)
- named:ƒ named(name)
- neverCalledWith:ƒ ()
- neverCalledWithMatch:ƒ ()
- notCalled:true
- printf:ƒ (format)
- reset:ƒ ()
- resetHistory:ƒ ()
- returnValues:[]
- returned:ƒ ()
- secondCall:null
- spyCall:ƒ ()
- thirdCall:null
- thisValues:[]
- threw:ƒ ()
- throwArg:ƒ ()
- toString:ƒ toString()
- withArgs:ƒ ()
- yield:ƒ ()
- yieldOn:ƒ ()
- yieldTo:ƒ ()
- yieldToOn:ƒ ()
For example, a spy can tell us how many times a function was called ( spy.callCount
), what arguments each call had ( spy.args[0]
), what values were returned ( spy.returnValues[0]
), what errors were thrown ( spy.exceptions[0]
),
etc.
Here we assert that callback
function is called once:
function myFunction(callback){
callback();
}
describe('myFunction', function() {
it('should call the callback function', function() {
const callback = sinon.spy();
myFunction(callback);
sinon.assert(callback.calledOnce);
});
});
Stubs
Stubs have all the functionality of spies, but instead of just spying on what a function does, a stub completely replaces it. In other words, when using a spy, the original function still runs, but when using a stub, it doesn’t.
This makes stubs perfect for faking network requests or database queries.
We can create anonymous stubsas above by calling sinon.stub
with no parameters:
const stub = sinon.stub();
or we can replaces object.method
with a stub function:
const stub = sinon.stub(object, "method");
The function sinon.stub
returns a Stub
object, which can be called like a function, but also contains properties with information on any calls made to it. Here is the list of properties for a Stub
object that can be accessed:
- addBehavior:ƒ ()
- alwaysCalledOn:ƒ ()
- alwaysCalledWith:ƒ ()
- alwaysCalledWithExactly:ƒ ()
- alwaysCalledWithMatch:ƒ ()
- alwaysCalledWithNew:ƒ ()
- alwaysReturned:ƒ ()
- alwaysThrew:ƒ ()
- args:[]
- behaviors:[]
- callArg:ƒ ()
- callArgOn:ƒ ()
- callArgOnWith:ƒ ()
- callArgWith:ƒ ()
- callCount:0
- callIds:[]
- callThrough:ƒ ()
- called:false
- calledAfter:ƒ calledAfter(spyFn)
- calledBefore:ƒ calledBefore(spyFn)
- calledImmediatelyAfter:ƒ calledImmediatelyAfter(spyFn)
- calledImmediatelyBefore:ƒ calledImmediatelyBefore(spyFn)
- calledOn:ƒ ()
- calledOnce:false
- calledThrice:false
- calledTwice:false
- calledWith:ƒ ()
- calledWithExactly:ƒ ()
- calledWithMatch:ƒ ()
- calledWithNew:ƒ ()
- callsArg:ƒ ()
- callsArgAsync:ƒ ()
- callsArgOn:ƒ ()
- callsArgOnAsync:ƒ ()
- callsArgOnWith:ƒ ()
- callsArgOnWithAsync:ƒ ()
- callsArgWith:ƒ ()
- callsArgWithAsync:ƒ ()
- callsFake:ƒ ()
- create:ƒ create(stubLength)
- createBehavior:ƒ ()
- createStubInstance:ƒ (constructor)
- defaultBehavior:null
- displayName:“stub“
- errorsWithCallStack:[]
- exceptions:[]
- firstCall:null
- formatters:{c: ƒ, n: ƒ, D: ƒ, C: ƒ, t: ƒ, …}
- func:ƒ ()
- get:ƒ ()
- getCall:ƒ getCall(i)
- getCalls:ƒ ()
- id:“spy#3“
- instantiateFake:ƒ create(stubLength)
- invoke:ƒ invoke(func, thisValue, args)
- invokeCallback:ƒ ()
- isPresent:ƒ ()
- isSinonProxy:true
- lastCall:null
- matches:ƒ (args, strict)
- matchingFakes:ƒ (args, strict)
- named:ƒ named(name)
- neverCalledWith:ƒ ()
- neverCalledWithMatch:ƒ ()
- notCalled:true
- onCall:ƒ onCall(index)
- onFirstCall:ƒ onFirstCall()
- onSecondCall:ƒ onSecondCall()
- onThirdCall:ƒ onThirdCall()
- printf:ƒ (format)
- rejects:ƒ ()
- reset:ƒ ()
- resetBehavior:ƒ ()
- resetHistory:ƒ ()
- resolves:ƒ ()
- resolvesThis:ƒ ()
- returnValues:[]
- returned:ƒ ()
- returns:ƒ ()
- returnsArg:ƒ ()
- returnsThis:ƒ ()
- secondCall:null
- set:ƒ ()
- spyCall:ƒ ()
- thirdCall:null
- thisValues:[]
- threw:ƒ ()
- throwArg:ƒ ()
- throws:ƒ ()
- throwsArg:ƒ ()
- throwsException:ƒ ()
- toString:ƒ toString()
- usingPromise:ƒ ()
- value:ƒ ()
- withArgs:ƒ ()
- yield:ƒ ()
- yieldOn:ƒ ()
- yieldTo:ƒ ()
- yieldToOn:ƒ ()
- yields:ƒ ()
- yieldsAsync:ƒ ()
- yieldsOn:ƒ ()
- yieldsOnAsync:ƒ ()
- yieldsRight:ƒ ()
- yieldsRightAsync:ƒ ()
- yieldsTo:ƒ ()
- yieldsToAsync:ƒ ()
- yieldsToOn:ƒ ()
- yieldsToOnAsync:ƒ ()
Here we’re replacing Ajax method on jQuery with sinon.stub
. This means the request is never sent, and we don’t need a server or anything.
it("test should fake successful ajax request", function () {
sinon.stub(jQuery, "ajax").yieldsTo("success", [1, 2, 3]);
jQuery.ajax({
success: function (data) {
assertEquals([1, 2, 3], data);
}
});
})