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:

var player = { x: 10, y: 10, vx: 0, getCoordinates: function() { return { x: this.x, y: this.y } }, moveRight: function() { this.vx += 1; return this; }, animate: function() { this.x += this.vx; return this; } //, ... }; 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 var player = { x : 10 , y : 10 , vx : 0 , getCoordinates : function ( ) { return { x : this . x , y : this . y } } , moveRight : function ( ) { this . vx += 1 ; return this ; } , animate : function ( ) { this . x += this . vx ; return this ; } //, ... } ;

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.

player.getCoordinates(); // Object {x: 10, y: 10} sinon.stub( player, 'getCoordinates' ).returns( { x: 0, y: 10 } ); player.moveRight().animate(); player.getCoordinates(); // Object {x: 0, y: 10} player.getCoordinates.restore(); player.getCoordinates(); // Object {x: 11, y: 10} 1 2 3 4 5 6 7 8 9 10 11 12 13 player . getCoordinates ( ) ; // Object {x: 10, y: 10} sinon . stub ( player , 'getCoordinates' ) . returns ( { x : 0 , y : 10 } ) ; player . moveRight ( ) . animate ( ) ; player . getCoordinates ( ) ; // Object {x: 0, y: 10} player . getCoordinates . restore ( ) ; player . getCoordinates ( ) ; // Object {x: 11, y: 10}

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.

var MyModel = Backbone.Model.extend({ defaults: function() { subModel1: new SubModel1(), subModel2: new SubModel2() }, parse: function( response ) { this.subModel1.reset( response.key1, { parse: true } ); this.subModel2.reset( response.key2, { parse: true } ); return _.omit( response, 'key1', 'key2' ); } }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 var MyModel = Backbone . Model . extend ( { defaults : function ( ) { subModel1 : new SubModel1 ( ) , subModel2 : new SubModel2 ( ) } , parse : function ( response ) { this . subModel1 . reset ( response . key1 , { parse : true } ) ; this . subModel2 . reset ( response . key2 , { parse : true } ) ; return _ . omit ( response , 'key1' , 'key2' ) ; } } ) ;

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.

var myModel = new MyModel(); // Testing subModel1 only, ignoring the rest sinon.stub( myModel, 'parse', function( response ) { this.subModel1.reset( response.key1, { parse: true } ); return {}; }); 1 2 3 4 5 6 7 8 9 var myModel = new MyModel ( ) ; // Testing subModel1 only, ignoring the rest sinon . stub ( myModel , 'parse' , function ( response ) { this . subModel1 . reset ( response . key1 , { parse : true } ) ; return { } ; } ) ;

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.

// Replace object methodName with stubFunction sinon.stub( object, 'methodName', stubFunction ); // Replace object methodName with (function() {}) sinon.stub( object, 'methodName' ); // Replace all methods of object with (function() {}) sinon.stub( object ); // Anonymous stub var myStub = sinon.stub(); 1 2 3 4 5 6 7 8 9 10 11 12 13 // Replace object methodName with stubFunction sinon . stub ( object , 'methodName' , stubFunction ) ; // Replace object methodName with (function() {}) sinon . stub ( object , 'methodName' ) ; // Replace all methods of object with (function() {}) sinon . stub ( object ) ; // Anonymous stub var myStub = sinon . stub ( ) ;

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

// Restore a stubbed method object.methodName.restore(); // Restore a stubbed object object.restore(); 1 2 3 4 5 6 7 // Restore a stubbed method object . methodName . restore ( ) ; // Restore a stubbed object object . restore ( ) ;

Specifying Stub Behavior

The following test suite demonstrates the basic usage of stubs.

describe( 'SinonJs Stubs', function() { beforeEach( function() { this.f = sinon.stub(); }); it( 'should return "stubbed value"', function() { this.f.returns( 'stubbed value' ); expect( this.f() ).to.equal( 'stubbed value' ); }); it( 'should throw an error with text ReferenceError', function() { this.f.throws( new Error( 'ReferenceError' ) ); expect( this.f ).to.throw( 'ReferenceError' ); }); it( 'should change behavior based on arguments', function() { this.f.withArgs( 5, true ).returns( true ); this.f.withArgs( 3 ).returns( false ); expect( this.f( 5, true ) ).to.be.true; expect( this.f( 3 ) ).to.be.false; }); it( 'should return different values on its first 5 calls', function() { this.f.onFirstCall().returns( 1 ); this.f.onSecondCall().returns( 2 ); this.f.onThirdCall().returns( 3 ); this.f.onCall( 3 ).returns( true ); this.f.onCall( 4 ).returns( false ); expect( this.f() ).to.equal( 1 ); expect( this.f() ).to.equal( 2 ); expect( this.f() ).to.equal( 3 ); expect( this.f() ).to.be.true; expect( this.f() ).to.be.false; }); it( 'should return its second argument', function() { this.f.returnsArg( 1 ); expect( this.f(true, 5) ).to.equal( 5 ); }); it( 'should call its first argument', function() { var spy = sinon.spy(); this.f.callsArg( 0 ); f( spy ); expect( spy.calledOnce ).to.be.true; }); it( 'should call its first argument with arguments 5, true', function() { var spy = sinon.spy(); this.f.callsArgWith( 0, 5, true ); f( spy ); expect( spy.calledOnce ).to.be.true; expect( spy.calledWith( 5, true ) ).to.be.true; }); it( 'should call its first argument with a defined context', function() { var context = {}; var spy = sinon.spy(); this.f.callsArgOn( 0, context ); f( spy ); expect( spy.calledOnce ).to.be.true; expect( spy.calledOn( context ) ).to.be.true; }); it( 'should call its first argument with a defined context and with argument 2', function() { var context = {}; var spy = sinon.spy(); this.f.callsArgOnWith( 0, context, 2 ); f( spy ); expect( spy.calledOnce ).to.be.true; expect( spy.calledOn( context ) ).to.be.true; expect( spy.calledWith( 2 ) ).to.be.true; }); }); 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 describe ( 'SinonJs Stubs' , function ( ) { beforeEach ( function ( ) { this . f = sinon . stub ( ) ; } ) ; it ( 'should return "stubbed value"' , function ( ) { this . f . returns ( 'stubbed value' ) ; expect ( this . f ( ) ) . to . equal ( 'stubbed value' ) ; } ) ; it ( 'should throw an error with text ReferenceError' , function ( ) { this . f . throws ( new Error ( 'ReferenceError' ) ) ; expect ( this . f ) . to . throw ( 'ReferenceError' ) ; } ) ; it ( 'should change behavior based on arguments' , function ( ) { this . f . withArgs ( 5 , true ) . returns ( true ) ; this . f . withArgs ( 3 ) . returns ( false ) ; expect ( this . f ( 5 , true ) ) . to . be . true ; expect ( this . f ( 3 ) ) . to . be . false ; } ) ; it ( 'should return different values on its first 5 calls' , function ( ) { this . f . onFirstCall ( ) . returns ( 1 ) ; this . f . onSecondCall ( ) . returns ( 2 ) ; this . f . onThirdCall ( ) . returns ( 3 ) ; this . f . onCall ( 3 ) . returns ( true ) ; this . f . onCall ( 4 ) . returns ( false ) ; expect ( this . f ( ) ) . to . equal ( 1 ) ; expect ( this . f ( ) ) . to . equal ( 2 ) ; expect ( this . f ( ) ) . to . equal ( 3 ) ; expect ( this . f ( ) ) . to . be . true ; expect ( this . f ( ) ) . to . be . false ; } ) ; it ( 'should return its second argument' , function ( ) { this . f . returnsArg ( 1 ) ; expect ( this . f ( true , 5 ) ) . to . equal ( 5 ) ; } ) ; it ( 'should call its first argument' , function ( ) { var spy = sinon . spy ( ) ; this . f . callsArg ( 0 ) ; f ( spy ) ; expect ( spy . calledOnce ) . to . be . true ; } ) ; it ( 'should call its first argument with arguments 5, true' , function ( ) { var spy = sinon . spy ( ) ; this . f . callsArgWith ( 0 , 5 , true ) ; f ( spy ) ; expect ( spy . calledOnce ) . to . be . true ; expect ( spy . calledWith ( 5 , true ) ) . to . be . true ; } ) ; it ( 'should call its first argument with a defined context' , function ( ) { var context = { } ; var spy = sinon . spy ( ) ; this . f . callsArgOn ( 0 , context ) ; f ( spy ) ; expect ( spy . calledOnce ) . to . be . true ; expect ( spy . calledOn ( context ) ) . to . be . true ; } ) ; it ( 'should call its first argument with a defined context and with argument 2' , function ( ) { var context = { } ; var spy = sinon . spy ( ) ; this . f . callsArgOnWith ( 0 , context , 2 ) ; f ( spy ) ; expect ( spy . calledOnce ) . to . be . true ; expect ( spy . calledOn ( context ) ) . to . be . true ; expect ( spy . calledWith ( 2 ) ) . to . be . true ; } ) ; } ) ;

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.