Jasmine mock for data service in Knockout

Using RequireJS makes life easier injecting dependencies like data services into Knockout components. However, there is no direct way of unit testing calls to such data services and need a different approach to be able to create a jasmine mock for an injected data service.

Let’s start with a simplified JavaScript data service.

define(["services/http.service"], function ($http) {
"use strict";

    function testDataService() {

        return {
            getItems: getItems
        };

        function getItems() {
            return $http.get(basePath + "api/items")
                .done(callSuccess);

            function callSuccess(response) {
                return response;
            }
        }
    }

    return testDataService();
});

And a Knockout component viewmodel code that makes one call to a data service to get some items from a REST API.

define(["knockout", "services/test-data.service"],
function (ko, testDataService) {
        function testViewModel() {
            var self = this;

            self.items = ko.observableArray([]);

            testDataService.getItems().done(function (data) {
                ko.utils.arrayForEach(data, function (obj) {
                    self.items.push(obj);
                });
            });
        }

    return testViewModel;
});

At this point we can’t spy on testDataService or create a jasmine mock for testDataService , since we do not hold any reference to it. To solve this problem we can add an optional argument to the constructor to pass in mock reference when needed, and if the reference is undefined, simply use the global reference to data service. This way original functionality stays the same, and we can unit the data service calls. So after small modifications view model looks following.

define(["knockout", "services/test-data.service"],
function (ko, testDataService) {
    // Add optional argument for data service to be passed in
    function testViewModel(params, dataservice) {
        var self = this;
        // use mock, or the global data service if mock is undefined
        self.dataservice = dataservice || testDataService;
        self.items = ko.observableArray([]);

        // make sure to call local data service reference
        self.dataservice.getItems().done(function (data) {
            ko.utils.arrayForEach(data, function (obj) {
                self.items.push(obj);
            });
        });
    }

    return testViewModel;
});

Please note that first parameter in the constructor has to be parameters in components.

And now we can write a simple test to make sure data service’s getItems() method was called, and self.items() array got two items pushed into it. We create jasmine spy object with a single method and mock it’s return value. Code below.

define(["components/test.viewmodel"],
function (testViewModel) {
    describe("Test View Model", function () {

        it("self.items get two objects pushed from getItems() response of two objects.",     function () {
            // Arrange
            var params = {};
            var d = $.Deferred(); // promise
            d.resolve([{ id: 1 }, { id: 2 }]); // resolve with two items array

            var datasetserviceSpy = jasmine.createSpyObj("datasetserviceSpy", ["getItems"]);
            datasetserviceSpy.getItems.and.returnValue(d); // mock return value

            // Act
            var self = new testViewModel(params, datasetserviceSpy);

            // Assert
            expect(datasetserviceSpy.getItems).toHaveBeenCalled();
            expect(self.items().length).toBe(2);
        });
    });
}

And running the test gives following output. Success!

jasmine mock test pass

The same data service can be used in the view model, and when unit testing it can be simple overridden with a jasmine mock data service now. Nice and easy!