Recently I've been working with clientside unit testing for an Angular project. The tests I've been creating have been using Jasmine for unit testing and Sinon (http://sinonjs.org) for spies, stubs and mocks, so I thought it might be useful to quickly blog about what I've learnt using Sinon.

As mentioned before, using Sinon gives us the ability to create spies, stubs and mocks.

Spies

Spies allow us to test that the required calls have been made in the method under test. More specifically, when using a spy we can check the arguments, return value and any exceptions thrown from a call.

Stubs

Stubs offer the same functionality as spies, but additionally they allow us to wrap an original function with a stub meaning our test will call the stubbed function and not the original function.

Mocks

Mocks offer the same functionality as stubs and mocks, but additionally we can use pre-programmed expectations. Hence a mock can fail your test if it isn't used as you would expect.

When should I use which?

As you can see, Mocks offer the most functionality (combining elements of spies and stubs with pre-programmed expectations). So, when choosing which to use I always try to use objects or approaches that provide only the functionality I require. As this means there's less scope for unexpected behaviour.

So, (very generally speaking) if you need to test that a function has been called on an object or test it's return value use Spies; if you need to replace an original function with a test function use Stubs; if you need to include pre-programmed expectations in your tests us Mocks.

Code Examples

Now I'm going to run through some very basic code examples to provide an overview of how Sinon spies, stubs and mocks are used in clientside tests. I'm going to test a basic AngularJS controller using Jasmine for the testing framework and run my tests in Visual Studio using Chutzpah via a VS add-in.

To begin with, I've got a very simple controller and dataContext:

Products Controller

(function () {
    'use strict';

    var controllerId = 'products';
    angular.module('app').controller(controllerId, [
        'common', 'dataContext',
        function (common, dataContext) {

            var vm = this;
            vm.products = [];
            activate();

            function activate() {
                vm.products = dataContext.getProducts();

                common.activateController([], controllerId);
            }
        }]);
})();

Data Context

(function () {
    'use strict';

    var serviceId = 'dataContext';
    angular.module('app').factory(serviceId, [
        function () {
            var service = {
                getProducts: getProducts,
                getOrders: getOrders
            };

            init();

            return service;

            function init() {
                // Implementation...
            }

            function getProducts() {
                return [
                    { id: 1, name: "Product 1" },
                    { id: 2, name: "Product 2" },
                    { id: 3, name: "Product 3" },
                    { id: 4, name: "Product 4" },
                    { id: 5, name: "Product 5" }
                ];
            }

            function getOrders() {
                // Implementation...
            }
        }]);
})();

Testing with Spies

Now I'm going to test that when the controller is activated that the products array is populated from the dataContext.getProducts function.

(function () {

        describe("products Controller", function () {

            var testContext = { 
                $controller: null,
                stubs: [],
                mocks: [],
                spies: []
            };

            beforeEach(function () {
                module('app');

                inject(function ($controller, dataContext) {
                    testContext.$controller = $controller;

                    // Create spy and add to testContext.spies for restoration later.
                    testContext.spies['getProducts'] = sinon.spy(dataContext, "getProducts");
                });
            });

            afterEach(function () {
                // Restore any spies
                for (var key in testContext.spies) {
                    testContext.spies[key].restore();
                }
            });

            it('activation loads products list', inject(function (common, dataContext) {
                // Arrange/Act: Create and initialize the controller
                var controller = testContext.$controller('products', { common: common, dataContext: dataContext });

                // Assert: Products has been populated
                expect(controller.products).not.toBeNull();

                // Assert: dataContext.getProducts was called
                sinon.assert.calledOnce(testContext.spies['getProducts']);
            }))
        });
    })();

In the above test, we create a spy on the getProducts function of the dataContext object. Then in the test assertions we use Jasmine to test that the controller.products value is not null. Finally, we use sinon assertions to test that the spy function was called at least once.

Testing Using Stubs

Now suppose our dataContext makes a call to a REST API or some other component that we don't want to call during our test. So we need to replace the dataContext.getProducts function with our own implementation for this test.

(function () {

        describe("Products Controller", function () {

            var testContext = { 
                $controller: null,
                products: [ {id: 1, name: "product 1"} , {id: 2, name: "product 2"} ],
                stubs: [],
                mocks: [],
                spies: []
            };

            beforeEach(function () {
                module('app');

                inject(function ($controller, dataContext) {
                    testContext.$controller = $controller;

                    // Create stub and add to testContext.stubs for restoration later.
                    testContext.stubs['getProducts'] = sinon.stub(dataContext, "getProducts", function () {
                        // Replace the getProducts function with our own implementation.
                        return testContext.products;
                    });
                });
            });

            afterEach(function () {
                // Restore any stubs
                for (var key in testContext.stubs) {
                    testContext.stubs[key].restore();
                }
            });

            it('activation loads products list', inject(function (common, dataContext) {
                // Arrange/Act: Create and initialize the controller
                var controller = testContext.$controller('products', { common: common, dataContext: dataContext });

                // Assert: Products has been populated
                expect(controller.products).not.toBeNull();

                // Assert: dataContext.getProducts was called
                sinon.assert.calledOnce(testContext.stubs['getProducts']);

                // Assert: dataContext.getProducts returned our testContext.products array
                expect(controller.products).toEqual(testContext.products);
            }))
        });
    })();

The above spec replaces the dataContext.getProducts function with our own implementation that simply returns the testContext.products array. Then in the test we assert that the controller.products array has been populated and that the dataContext.getProducts function was called once. In addition, we also check that the dataContext.getProducts function returns the list of products from testContext.products.

Test Using Mocks

Now we'd like to completely mock the dataContext object so we can pre-program expectations of the number of times functions are called and what they return.

(function () {

    describe("Products Controller", function () {

        var testContext = {
            $controller: null,
            products: [{ id: 1, name: "product 1" }, { id: 2, name: "product 2" }],
            stubs: [],
            mocks: [],
            spies: []
        };

        beforeEach(function () {
            module('app');

            inject(function ($controller, dataContext) {
                testContext.$controller = $controller;

                // Create stub and add to testContext.stubs for restoration later.
                testContext.mocks['dataContext'] = sinon.mock(dataContext);

                // Pre-programmed expectation: getProducts is called once and returns our products list.
                testContext.mocks['dataContext'].expects("getProducts").once()
                                                .returns(testContext.products);

                // Pre-programmed expectation: getOrders is never called.
                testContext.mocks['dataContext'].expects("getOrders").never();
            });
        });

        afterEach(function () {
            // Restore any stubs
            for (var key in testContext.mocks) {
                testContext.mocks[key].restore();
            }
        });

        it('activation loads products list', inject(function (common, dataContext) {
            // Arrange/Act: Create and initialize the controller
            var controller = testContext.$controller('products', { common: common, dataContext: dataContext });

            // Assert: Products has been populated
            expect(controller.products).not.toBeNull();

            // Assert: dataContext.getProducts returned our testContext.products array
            expect(controller.products).toEqual(testContext.products);

            // Assert: Verify mock pre-programmed expectations
            testContext.mocks['dataContext'].verify();
        }))
    });
})();

In the beforeEach function we create a mock of the dataContext object. Now we pre-program the expectations we have, which in this case are that dataContext.getProducts is called once and returns our testContext.products array. In addition to this we also pre-program the expectation that the dataContext.getOrders function is never called.

Then in the test, we assert that the controller.products array has been populated, that the dataContext.getProducts function returns the expected array of products and finally the .verify() call asserts that all of the pre-programmed expectations of our mock have been met.


In conclusion, clientside testing is more available and easier to pick up that ever and I'm really enjoying learning more and more about it.