Image source — google image, edited with Microsoft PowerPoint

Unit Testing with Mocha, Chai, and Sinon — SPY

“Unit testing finds problems early in the development cycle. This includes both bugs in the programmer’s implementation and flaws or missing parts of the specification for the unit” — Wikipedia

The above statement describes the purpose of good Unit Testing, so we need to ensure that besides the coding we are also writing some proper unit tests which can ensure the functionality coverage and as well as help to find major or critical bugs in the early stage of the software development lifecycle.

In this article, we will understand the process of writing some Unit Tests by implementing the Spy concept along with SinonJS, Mocha unit testing framework, and Chai assertion library. The rest of the major concepts of unit testing (i.e. Mock and Stub ) we will discuss in other articles, here we will only concentrate on the implementation of Spies .

What is test Spy?

“A test spy is a function that records arguments, return value, the value of this and exception thrown (if any) for all its calls. There are two types of spies: Some are anonymous functions, while others wrap methods that already exist in the system under test.” -SinonJS

Apart from the technical description, to understand the Spy we can consider an example of a CCTV camera which records all of the activities of that specific place where it is installed and we can fetch the recorded videos when needed. So, just like that, Spy is recording all of the activities of a function and we can validate the returned value of it, we can verify the arguments, throws exceptions and so on .

Let’s understand with an example.

class Calculator {
constructor(){
console.log("Let's calculate");
}
add(number1, number2){
return number1 + number2;
}
}
module.exports = Calculator

So, here we have a very simple code snippet of Calculator class, which only contains one add() function, which returns the sum of two numbers. After that, we are exporting this class so that we can call the function with the help of Object of this class from some another class or maybe test class.

Let’s test this code.

Approach 1: First, we will try to test add function without using any spies , here we will just validate the return value with the help of chai’s expect

var calculator = require("../src/Calculator");
var calObject = new calculator();
var chai = require("chai");
var expect = chai.expect;
describe("Test Calculator", function(){
it("Test add method with expect", function(){
expect(calObject.add(2,3)).to.be.equals(5);
})
})

Here is the test result:

Test result, generated by Mocha on console
Test result, generated by Mocha on console

Wow! it works 👌

Now, couple of queries may arrive on our mind 🤔

1. How can we determine whether the add() has been called with 2 & 3 or not? because the expected result (here 5 ) can be returned with other numbers also.
2. How can we determine the number of times add() has been called?
3. How can we determine whether the add() is never been called with some specific arguments?
4. Can we check whether add() is throwing some exception or not? and so on.

The implementation of Spies can solve these doubts 😎. So here is our Approach 2 (Using spies) :
So, before starts with spies, first we need to import sinon in our test file.

var sinon = require("sinon");

After that, sinon.spy() needs to be called, which will provide a wrapper on add() and store the reference in spyCalculator

var spyCalculator = sinon.spy(calObject, "add");

Note: Here, calObject is an object of Calculator class and add is the function name passed as a parameter of spy() .

Let’s validate the returned value of add() with the help of spy.returned(obj);

describe ("Spies on Calculator", function(){
var spyCalculator = sinon.spy(calObject, "add");
it("Test returned result of add", function(){
calObject.add(2,3);
expect(spyCalculator.returned(5)).to.be.true;
})
})

With the help of spy.calledWith() we can validate the specified arguments which has been passed while calling out the add function

describe ("Spies on Calculator", function(){
var spyCalculator = sinon.spy(calObject, "add");
it("Test add method called with specified arguments", function(){
calObject.add(6,3);
expect(spyCalculator.calledWith(6,3)).to.be.true;
})
})

The spy.neverCalledWith() can determine add() is never been called with the specified arguments.

describe ("Spies on Calculator", function(){
var spyCalculator = sinon.spy(calObject, "add");
it("Test add method never called with specified arguments",
function(){
calObject.add(2,3);
expect(spyCalculator.neverCalledWith(5,6)).to.be.true;
})
})

With the help of spy.withArgs(3,3).calledOnce can determine that, add() is called with 3,3 as it’s arguments only once

describe ("Spies on Calculator", function(){
var spyCalculator = sinon.spy(calObject, "add");
it("Test number of times add is called with specific arguments", function(){
calObject.add(4,3);
expect(spyCalculator.withArgs(4,3).calledOnce).to.be.true;
})
})

We can also determine whether the add() has thrown some exception or not

describe ("Spies on Calculator", function(){
var spyCalculator = sinon.spy(calObject, "add");
it("Test add method throw exception", function(){
calObject.add(6,6);
expect(spyCalculator.threw()).to.be.false;
})
})

So, now if we perform the final execution then the result would be

Awesome! All tests has been passed ✔

That’s all for this article. But this is not the the end. The other topics of Mock and Stub will be discussed soon and the links will be shared along with this article only.

If you would like to refer the entire test spec files then please check out the GitHub for more details.

Thanks for your patience. Hope this article will help you to understand the concept of Spy in unit testing.

Sr. SDET by profession with 6+ years of experience in the Software Testing domain. Passionate about learning.