Spies in Jasmine & Sinon
π TL;DR π
- Terminology used by Jasmine, Sinon and other testing frameworks is not the same: one person's spy is another person's stub
- The equivalent of Sinon's
sinon.spy(foo,'bar')
in Jasmine isspyOn(foo,'bar').and.callThrough()
So you’re testing. In JavaScript. Perhaps you want to test:
- whether a method has been called
- what arguments the method was called with
- what the method returns
A reasonable request. You turn to your testing framework and it turns back to you and it says “Bob, you need to use a spy.” Fine.
Maybe you have to use Jasmine as your testing framework at work, but really you find Mocha + Chai more readable, so you use that for your personal projects. Fine, your choice. Just don’t forget that Mocha doesn’t come with spies built-in, so you need to use another library to provide that. You choose Sinon because … well, everyone else does so it’s easy to find answers on StackOverflow. Fine. But then you make the naΓ―ve mistake of thinking the APIs for Mocha/Chai/Sinon and Jasmine are the same! Nope. Observe.
Jasmine Spy !== Sinon Spy
Spies
We set a spy on a method so that we can tell whether it’s been called, or record what arguments it was called with. The difference is that a Sinon spy calls the original implementation of a method by default, whereas you have to explicitly request this behaviour with Jasmine:
Jasmine | Sinon |
---|---|
spyOn(foo,'bar').and.callThrough() |
sinon.spy(foo,'bar') |
N.B. The new chainable syntax in Jasmine 2.0
What does this mean? It means that when you spy on the bar()
method, the actual method still executes. The alternative in Jasmine is just to call spyOn(foo,'bar')
, which will just stub bar()
: it will be called, and it won’t throw an error, but the actual bar()
method itself will not run.
Stubs
How about if you want to ‘stub’ out the return value of your method, so that it returns a set value every time? The Jasmine and Sinon syntax is different for this too:
Jasmine | Sinon |
---|---|
spyOn(foo,'bar').and.returnValue(42) |
sinon.stub(foo,'bar').returns(42) |
Sinon’s API makes use of a different method now, stub()
, whereas Jasmine chains onto the original ‘spy’, but they both do effectively the same thing.
What’s your point?
You say tomato, I say tomato. π
One man’s stub π is another man’s mock π, which is another person’s spy π, which is another person’s test double π. When you assume that you know that you know what that testing framework is talking about, you make an ASS out of U and ME.
This post was inspired by the day I lost wondering why my Jasmine spy wasn’t behaving how I thought it should. Curse you .and.callThrough()
!!