Previous: part 4 – Next: part 6
You can also find the code below in the src/unit-test-3 dir of my blog code repository.
Spying sounds really cool.
I have to disappoint you. Unfortunately (or maybe not), spying in the world of unit testing is nowhere as exciting as spying in real life. At least, that’s what I think. Maybe the view we non-spies have on spying, created by all these films and series, is completely incorrect. Who knows, maybe unit test spying is more exciting than real world spying.
Anyway, spying. A test spy is a way to verify that a specific call, method or function has been called correctly.
Sounds pretty vague, right? Let’s look at some code to make that more concrete.
// shop.js var myLogger = require('./mylogger'); function calculateTotal(items) { var totalPrice = 0; var totalItems = 0; items.forEach(function(item) { totalItems += item.count; totalPrice += item.count * item.price; }); if (totalPrice > 1000) { myLogger.log('A probably order of ' + totalPrice + ' is being considered!'); } return { totalPrice: totalPrice, totalItems: totalItems, }; } module.exports = { calculateTotal: calculateTotal };
This is a module for your web shop, with a function in it to calculate the total price. Of course you are going to be very excited every time a large order is about to happen, so that’s why you decided to log every calculation where the total is over 1000.
For now, you’re just logging this to console, using mylogger.js :
// mylogger.js function log(message) { console.log(message); } module.exports = { log: log };
How can we test whether the reporting mechanism works?
We could try to fiddle around with capturing STDOUT or overwriting console.log . That’s certainly an option, but it’s a bit crude. Besides, if we change the log function to send an email instead of writing to console, it won’t work anymore.
What are we really interested in? Well, we want to know whether the log function has been called by calculateTotal. We don’t need the actual log function to be called – this might even cause problems, if log would send an email or write to a database.
Here, a spy comes in handy. We replace the myLogger.log call by a spy. This has 2 advantages:
- We can see exactly with which arguments log was called
- The actual behaviour of log is not executed
Here’s one way of doing it, with the excellent sinon.js library:
// shop.spec.js require('should'); var sinon = require('sinon'); var shop = require('./shop'); var myLogger = require('./mylogger'); describe('calculateTotal', function() { it('should log a happy message for a large total', function() { var items = [ { price: 200, count: 4, }, { price: 300, count: 2, } ]; var logSpy = sinon.spy(myLogger, 'log'); shop.calculateTotal(items); logSpy.calledOnce.should.be.true; logSpy.getCall(0).args[0].should.equal('An order of 1400 is being considered!') }); });
Another situation where a spy could come in handy, is when you are calling a function that expects a callback. In that case, you can just create a spy with sinon.spy() and pass that as callback. Afterwards, you can verify that the callback was called with the right arguments. You can find an example in the sinon.js documentation.