Stubbing with SinonJs


In part 1 and part 2 of this series, we learned the basics of using Mocha and Chai. Part 3 introduced SinonJs spies. We will continue with the exploration of an additional utility belt responsible for isolating the tested functionality from the outside world. This isolation can be made on multiple levels: isolation on code level and isolation on system level.

Code level isolation

Unit testing takes the smallest testable chunks of your system and verifies their behavior in perfect isolation from its surroundings. Most objects have peers that can be:

  • dependencies: these peers are required for performing an operation
  • notifications: events, possibly inducing state changes
  • adjustments: change/adjust default behavior

For instance, a child view belonging to a composite view object or a model belonging to a view are both dependencies. Event driven communication, publish-subscribe act as notifications. Specifying a date formatter or currency formatter, the ability to replace bar charts with candlestick charts are both adjustments. If you don’t have a problem with reading Java code, a section is dedicated for object peers in the book Growing Object-Oriented Software Guided by Tests by Steve Freeman and Nat Pryce.

Automated tests have to be reliable. They always have to produce the same results, regardless the circumstances. In case a fault is inserted in the code, the exact location of the fault has to be pointed out by the failing tests. All other tests still have to pass, including the ones that use the faulty component as a dependency. This is achieved through stubbing.

Stubs

A stub replaces a peer of the tested object with a simplified implementation. Stubs come with the following benefits:

  • faults in peers do not affect the tested code as long as the peer is stubbed
  • in case a dependency does not exist yet, its stub can be implemented easily
  • resource intensive dependencies are executed faster
  • help you in debugging tested code

As an example, suppose we define a player character of a game with the following properties:

In the following example, we stub the getCoordinates method of the player object in a way that we expect it to return a fixed value regardless the internal state of the object.

Moving the player right increases its horizontal velocity by 1 unit. Animating passes time such that the horizontal speed of the player is added to its x coordinate. After the original getCoordinates method is restored, the method returns the expected x coordinate value 11.

Stubbing methods is a strong weapon in unit testing. We can detach methods we do not test by stubbing them and giving them a fixed return value. Whenever a test fails, we know that the fault does not come from the stubbed method. In addition, we don’t even have to implement all required methods to make a test work. When practicing test driven development, we often specify what methods we need in an object without taking care of the implementation. Stubs help us define meaningful tests while the code is under construction.

A BackboneJs Stubbing Example

Suppose that a model holds a reference of two nested submodels. Parsing of each submodel is invoked when parsing the original model.

If we are only interested in parsing the first submodel, we can stub the parse method of MyModel and replace it with a function that only takes care of parsing the first submodel. In addition, we are not interested in the return value of the parse method either, therefore we just return an empty object.

Basic Stub Usage

Stubbing a method replaces it with (function() {}). When a method name is not specified, all methods of an object are stubbed. A third argument to sinon.stub can replace the default empty stub function with an arbitrary function.

The restore method restores the originally implemented behavior to a stubbed method or to an object.

Specifying Stub Behavior

The following test suite demonstrates the basic usage of stubs.

The above list is not exhaustive as there are more ways to call arguments of a stubbed function. However, in practice, I hardly found these methods useful.

The last 4 examples combined spies and stubs. In practice, we often need to combine them. The next article of this series will introduce a way to combine spies and stubs with a very useful twist.

As the recipe spy + stub + twist contains the stub ingredient, it is not hard to figure out that we will continue exploring a tool achieving code level isolation. System level isolation will be introduced afterwards by detaching communication with a server and taking full control of the timer.

Learn ES6 in Practice

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