Automated software testing part 5: spying

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.

A spy hanging from a wire just above the floor

A real spy

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:

  1. We can see exactly with which arguments log  was called
  2. 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.

This entry was posted in software development, testing and tagged , , , , , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *