SinonJs Spies


This series introduces automated testing libraries for Javascript applications with Mocha, Chai and SinonJs. The first part covers the fundamentals of Mocha and Chai. The second part describes how Mocha and Chai can test DOM manipulations and handle asynchronous tests.

Mocha and Chai have mostly been covered. If you apply what you learned in the first two parts of the Automated Testing series, you should be fine. We will now extend our test suites with a utility belt serving multiple purposes:

  • Collect and verify runtime information on tested code (spies)
  • Isolate tested code from code that surrounds it (stubs, mocks)
  • Isolate the tested system from systems, storages and timing constraints surrounding it (fake server, fake timer)

SinonJs provides solutions helping you in achieving all three points. We will focus on the first point in the rest of this article: collecting and verifying runtime information on tested code.

Spies

Spies monitor conditions under which a function is executed. While keeping existing state and behavior, spies provide more information such as:

  • the number of times a function was called during a test
  • the arguments a function was called with
  • the return value of a function

I will show you how to use three types of spies: anonymous spies, function spies and method spies.

Anonymous spies: The following example could be a possible test case for Backbone Events providing event-driven communication in Backbone applications:

The empty Sinon spy constructor creates an anonymous spy capable of acting instead of any pieces of functionality. I personally found them useful when I wanted to know if an event was triggered with correct arguments, without executing the callback method. The example above demonstrates this use case by using Backbone’s publish-subscribe channels. When custom:event is triggered, the anonymous spy is executed checking if the callback was called with the proper arguments exactly once.

Function spies: function spies wrap a function keeping the original implementation and collecting runtime information.

The first test needs no further explanation, even the syntax of the SPY API is straightforward to read. It is worth noting that the first example can fully be solved using Mocha and Chai syntax from the first part of this series.

The second example is a bit more complex. If you have not implemented this test case, I encourage you to solve it now as you may learn a valuable lesson. In case you have implemented this test case and your function was only called once instead of 9 times, you will soon have an eureka effect.

The challenge of solving the recursive test is that the recipe of creating a spy under the reference fibonacciSpy and calling it does not work when trying to keep track of the call count. Think about it. Function spies keep the original behavior intact. The original behavior includes calling ‘fibonacci’ instead of fibonacciSpy. This means that the call count of fibonacciSpy will always be 1.

The solution is to replace the original fibonacci reference with its spied version inside the scope of the test.

Method spies: instead of spying on a function, the observed element is a method of an object. Don’t forget that spying on methods is a permanent change within an object. Therefore, the original behavior has to be restored before ending the test.

The following example demonstrates the usage of method spies using Backbone models. The test case verifies that the parse method of the model is called after a successful fetch. This tests builds on the foundations of creating asynchronous tests.

Spy API: We have used just a fraction of the features Sinon spies provide. The following test suite introduces the rest. In order to make the test suite self documenting, I will omit the description “should demonstrate the usage of … spy field or method”. The description includes the tested spy field or method name, followed by its type. In case of functions, the type notation will be demonstrated in the form arguments => returnValue. The type any denotes an arbitrary Javascript type.

The getCall method and the firstCall, secondCall, thirdCall and lastCall properties return a spy call having the following interface:

Matchers

I left out a couple of functions from the cheat sheet as they require the introduction of matchers. SinonJs matchers provides you with tools for describing your expectations in a compact way. These expectations are then used in assertions to verify whether the arguments or the return value match the expectation described in the matcher.

Using matchers is not mandatory. Some matchers are easier to express in other ways, while others rather act as syntactic sugar. One use case I highly recommend is checking if tested functions are always called with the same argument types and they also return the same argument type in each call.

All the above tests have been added to the Mocha-Chai-SinonJs reference on GitHub. Feel free to clone it or fork it.

After writing all 72 tests, I discovered that I started utilizing the expressive power of Mocha, Chai and SinonJs better than before. This exercise was very much like a short term memory training exercise. I suggest building on these foundations if you write automated tests on a regular basis. Alternatively, whenever you get stuck, you can look up these tests as a reference.

Code Isolation

Spying is just scratching the surface of what Sinon can provide you with. Creating stubs and mocks will allow you to write more reliable tests by allowing you to focus on code that matters and substitute code that does not matter in a test. In other words, tested code will work in a vacuum, surrounded by simplified dependencies that always act correctly under special, tested circumstances.

If you liked this article, read the complete series on test automation.

Learn ES6 in Practice

Sign up below to access an ES6 course with many exercises and reference solutions.