Angular JS – Testing a controller with its view, as for a directive with its template

var $scope, target;

inject(function ($rootScope, $controller, $compile, $templateCache) {
    $scope = $rootScope.$new();

    $controller("modalUsersController", {
        "$scope": $scope,
        "$locale": {labels: labelsMock},
        "$uibModalInstance": uibModalInstanceMock,
        "usersService": usersServiceMock
    });

    target = $compile($templateCache.get("modal-views/users.html"))($scope);
    $scope.$digest();
});

Node JS testing examples with Mocha, Proxyquire, Assert and Sinon

it("it should map the options", function (done) {
    var objMappingServiceMapSpy = sinon.spy(objMappingServiceMock, "map");

    target.callPublic(reqMock, resMock).then(function () {
        try {
            assert(objMappingServiceMapSpy.withArgs("HttpServiceOptions", optionsMock).calledOnce);
            done();
        }
        catch (err) {
            done(err);
        }
    });
});

describe("when the response is a success", function () {
    beforeEach(function () {
        requestPromiseMock = sinon.stub().returns(
            new Promise(function (resolve, reject) {
                resolve("api data");
            })
        );

        target = proxyquire("api/services/factories/httpServiceFactory", {
            "request-promise": requestPromiseMock,
            "api/services/api/apiConfigService": apiConfigServiceMock
        });

        instance = target.make("frankie");
    });

    it("it should return the data", function (done) {
        instance.call(optionsMock).then(function (data) {
            try {
                assert.deepEqual(data, "api data");
                done();
            }
            catch (err) {
                done(err);
            }
        });
    });
});

Angular Service with $http, $q and testing (test fixtures)

angular.module("myApp.services")
    .service("usersService", ["$http", '$q',
        function ($http, $q) {
            var _self = this;

            _self.getUserDetails = function () {
                return $q(function (resolve, reject) {
                    $http.get('/api/client/me/details')
                        .then(function (result) {
                            resolve(result.data);
                        })
                        .catch(function (error) {
                            reject(error);
                        });
                });
            };

            _self.setUserDetails = function (firstName, lastName) {
                return $q(function (resolve, reject) {
                    $http.post('/api/client/me/details', {
                        firstName: firstName,
                        lastName: lastName
                    })
                        .then(function (result) {
                            resolve(result.data);
                        })
                        .catch(function (error) {
                            reject(error);
                        });
                });
            };
        }]);

<hr />

describe("an usersService", function () {
    var target;

    var httpMock;
    var qMock;

    beforeEach(function () {
        httpMock = fixturesProvider.getSuccessHttpMock("api response");
        qMock = fixturesProvider.getQMock();

        module("myApp.services", function ($provide) {
            $provide.value("$http", httpMock);
            $provide.value("$q", qMock);
        });

        inject(function ($injector) {
            target = $injector.get("usersService");
        });
    });

    describe("#getUserDetails", function () {
        it("it should call the api", function () {
            spyOn(httpMock, "get").and.callThrough();
            target.getUserDetails();
            expect(httpMock.get).toHaveBeenCalledWith("/api/client/me/details")
        });

        it("it should return the result", function () {
            var result = target.getUserDetails();
            expect(result.resolved).toBeTruthy();
            expect(result.value).toBe("api response");
        });
    });

    describe("#setUserDetails", function () {
        it("it should call the api", function () {
            spyOn(httpMock, "post").and.callThrough();
            target.setUserDetails("John", "Doe");
            expect(httpMock.post).toHaveBeenCalledWith("/api/client/me/details", {
                firstName: "John",
                lastName: "Doe"
            })
        });

        it("it should return the result", function () {
            var result = target.setUserDetails("John", "Doe");
            expect(result.resolved).toBeTruthy();
            expect(result.value).toBe("api response");
        });
    });

    describe("when the api call fails", function () {
        beforeEach(function () {
            httpMock.get = fixturesProvider.getFailingHttpMethodMock(500);
            httpMock.post = fixturesProvider.getFailingHttpMethodMock(500);
        });

        describe("#getUserDetails", function () {
            it("it should return the error", function () {
                var result = target.getUserDetails();
                expect(result.resolved).toBeFalsy();
                expect(result.value.status).toBe(500);
            });
        });

        describe("#setUserDetails", function () {
            it("it should return the error", function () {
                var result = target.setUserDetails("John", "Doe");
                expect(result.resolved).toBeFalsy();
                expect(result.value.status).toBe(500);
            });
        });
    });
});

Angular JS – Test Fixtures

var fixturesProvider = new function () {
    var _this = this;

    //---------------
    // promises
    //---------------

    _this.getSuccessPromiseMock = function (result) {
        return {
            then: function (callback) {
                callback(result);
                return {
                    catch: function () {
                    }
                };
            }
        };
    };

    _this.getFailingPromiseMock = function (error) {
        return {
            then: function () {
                return {
                    catch: function (callback) {
                        callback(error);
                    }
                }
            }
        };
    };

    //---------------
    // $http
    //---------------

    _this.getHttpResultMock = function (result) {
        return {
            data: result
        };
    };

    _this.getHttpErrorMock = function (status) {
        return {
            status: status
        };
    };

    _this.getSuccessHttpMethodMock = function (result) {
        return function () {
            return _this.getSuccessPromiseMock(_this.getHttpResultMock(result))
        };
    };

    _this.getFailingHttpMethodMock = function (status) {
        return function () {
            return _this.getFailingPromiseMock(_this.getHttpErrorMock(status))
        };
    };

    _this.getSuccessHttpMock = function (result) {
        return {
            get: _this.getSuccessHttpMethodMock(result),
            post: _this.getSuccessHttpMethodMock(result)
        };
    };

    _this.getFailingHttpMock = function (status) {
        return {
            get: _this.getFailingHttpMethodMock(status),
            post: _this.getFailingHttpMethodMock(status)
        };
    };

    //---------------
    // services
    //---------------

    _this.getSuccessApiMethodMock = function (result) {
        return function () {
            return _this.getSuccessPromiseMock(result);
        };
    };

    _this.getFailingApiMethodMock = function (status) {
        return _this.getFailingHttpMethodMock(status);
    };

    //---------------
    // $q
    //---------------

    _this.getQMock = function () {
        return function (callback) {
            var result = null;

            callback(function (resolveValue) {
                result = {
                    resolved: true,
                    value: resolveValue
                };
            }, function (rejectValue) {
                result = {
                    resolved: false,
                    value: rejectValue
                };
            });

            return result;
        };
    };

    //---------------
    // modals
    //---------------

    _this.getUibModalMock = function () {
        return {
            open: function () {
                return {
                    result: {
                        then: function (callback) {
                            callback();
                        }
                    }
                };
            }
        };
    };

    _this.getUibModalInstanceMock = function () {
        return {
            dismiss: function () {
            },
            close: function () {
            }
        };
    };

    //---------------
    // $window
    //---------------

    _this.getWindowMock = function () {
        return {
            location: {
                href: ""
            }
        };
    };

    //---------------
    // $filter
    //---------------

    _this.getFilterMock = function () {
        return function () {
            return function () {
                return Array.prototype.join.call(arguments, "-");
            };
        };
    }
}();

Jasmine JS – Verifying that a mocked method has been called with SpyOn

var NumberGeneratorService = function () {
    this.getNumber = function (min, max) {
        return Math.floor(Math.random() * (max - min) + min);
    };
};
 
var NumberProvider = function (numberGeneratorService) {
    this.getNumbersCollection = function (size) {
        var result = [];
        for (var n = 0; n < size; n++) {
            result.push(numberGeneratorService.getNumber(1, 10));
        }
        return result;
    };
};
 
describe("Numbers Provide Test: ", function () {
    var _numberGeneratorServiceMock;
    var _numbersProvider;
 
    beforeEach(function () {
        _numberGeneratorServiceMock = {
            getNumber: function (min, max) {
            }
        };
        spyOn(_numberGeneratorServiceMock, "getNumber").and.callThrough();
 
        _numbersProvider = new NumberProvider(_numberGeneratorServiceMock);
    });
 
    it("It should call the Number Generator Service", function () {
        var result = _numbersProvider.getNumbersCollection(10);
 
        //We are checking that the getNumber()
        //method hav been called exactely 10 times
        expect(_numberGeneratorServiceMock.getNumber.calls.count()).toBe(10);
 
        //We can also check that the mocked method
        //has been called with the desired parameters
        expect(_numberGeneratorServiceMock.getNumber).toHaveBeenCalledWith(1, 10);
    });
});

Is possible to only check the type of an argument. This can be useful if, for instance, we want to test that a callback has been called without actually providing the callback itself. In order to do that we can user the jasmine.any() primitive. Here is an example:

expect(myObject.myMethod).toHaveBeenCalledWith(10, "my string arg", jasmine.any(Function));

Jasmine JS, Angular JS – Testing of an Angular Controller + Spy On

While testing a controller we can use the Angular’s $controller() method (instead of $inject.get()), which allow us to resolve the controller and to specify the mock dependencies in a single call as well.

SpyOn() Jasmine feature

We are using the spyOn() Jasmine’s feature as well, which allow us to check if a particular target’s method has been called. The spyOn() method allow as as well to mock the method’s code, or we can use the and.callThrough() method (as we are doing) to execute the real method’s code. To check if a method we was spying on was called we can use the toHaveBeenCalled() method within our tests.

Here is our test:

/// <reference path="../../../scripts/jquery-2.1.3.min.js" />
/// <reference path="../../../scripts/jasmine/jasmine.js" />
/// <reference path="../../../scripts/angular.min.js" />
/// <reference path="../../../scripts/angular-mocks.js" />
/// <reference path="../../hangman/hangmanBundle.js" />
 
describe("Hangman Controller:", function () {
    describe("When the controller is created", function () {
        var $scope, target;
 
        //Mock objects
        var mockHangmanService = {
            getKeyboard: function () {
                return new window.hangman.KeyboardModel();
            },
            getPhrase: function (phrase) {
                return new window.hangman.PhraseModel(phrase);
            }
        };
        var mockWordService = {
            getWord: function () {
                return {
                    then: function (callback) {
                        callback("The Test Phrase");
                    }
                };
            }
        };
        var mockHangmanConstants = {
            NS: window.hangman,
            MAX_ERRORS: 5
        };
 
        beforeEach(function () {
            module("hangmanApp.controllers");
            inject(function ($controller, $rootScope) {
 
                //Create the test $scope
                $scope = $rootScope.$new();
 
                //With spyOn() we can check if our mock methods were called.
                //.ans.callThrough() tells to really execute the mock methods
                //(since we could override their returned value from here).
                spyOn(mockHangmanService, "getKeyboard").and.callThrough(); //<- Jasmine 2.0 only! (With 1.0 it was .andCallThrough())
                spyOn(mockHangmanService, "getPhrase").and.callThrough();   //<- Jasmine 2.0 only! (With 1.0 it was .andCallThrough())
                spyOn(mockWordService, "getWord").and.callThrough();        //<- Jasmine 2.0 only! (With 1.0 it was .andCallThrough())
 
                //Resolve the hangmanController.
                //In this case we can use the $controller object to
                //create the controller directly passing its dependencies.
                //So we don't need to create a mock module and to use the
                //$injector object as we did for the hangmanService
                target = $controller("hangmanController", {
                    "$scope": $scope,
                    "hangmanService": mockHangmanService,
                    "wordService": mockWordService,
                    "hangmanConstants": mockHangmanConstants
                });
            });
        });
 
        it("It should have a keyboard", function () {
            expect($scope.keyboard).toBeDefined();
 
            //We can check this thanks to the spyOn()
            expect(mockHangmanService.getKeyboard).toHaveBeenCalled();
        });
 
        it("It should have a phrase", function () {
            expect($scope.phrase).toBeDefined();
 
            //We can check this thanks to the spyOn()
            expect(mockHangmanService.getPhrase).toHaveBeenCalled();
            expect(mockWordService.getWord).toHaveBeenCalled();
        });
 
        it("It should be possible to win", function () {
            expect($scope.playerWins()).toBe(false);
            expect($scope.endGame()).toBe(false);
            $scope.onKeyPress("t");
            $scope.onKeyPress("h");
            $scope.onKeyPress("e");
            $scope.onKeyPress("s");
            $scope.onKeyPress("p");
            $scope.onKeyPress("r");
            $scope.onKeyPress("a");
            expect($scope.playerWins()).toBe(true);
            expect($scope.endGame()).toBe(true);
        });
 
        it("It should be possible to lose", function () {
            expect($scope.playerLose()).toBe(false);
            expect($scope.endGame()).toBe(false);
            $scope.onKeyPress("x");
            $scope.onKeyPress("x");
            $scope.onKeyPress("x");
            $scope.onKeyPress("x");
            $scope.onKeyPress("x");
            expect($scope.playerLose()).toBe(true);
            expect($scope.endGame()).toBe(true);
        });
    });
});

Here’s our controller’s code

angular.module("hangmanApp.controllers").controller("hangmanController",
    ["$scope", "hangmanService", "wordService", "hangmanConstants",
        function ($scope, hangmanService, wordService, hangmanConstants) {
            $scope.errors = 0;
            $scope.keyboard = null;
            $scope.phrase = null;
 
            $scope.init = function () {
                $scope.errors = 0;
                $scope.keyboard = hangmanService.getKeyboard();
                wordService.getWord().then(function (word) {
                    $scope.phrase = hangmanService.getPhrase(word);
                });
            };
            $scope.onKeyPress = function (key) {
                $scope.keyboard.pressKey(key);
                if (!$scope.phrase.discoverCharacter(key)) {
                    $scope.errors++;
                    if ($scope.playerLose())
                        $scope.phrase.revealPhrase();
                }
            };
            $scope.playerWins = function () {
                if ($scope.phrase)
                    return $scope.errors < hangmanConstants.MAX_ERRORS
                        && $scope.phrase.phraseDiscovered();
                else
                    return false;
            };
            $scope.playerLose = function () {
                return $scope.errors == hangmanConstants.MAX_ERRORS;
            };
            $scope.endGame = function () {
                return $scope.playerWins() || $scope.playerLose();
            };
            $scope.init();
        }]);

Jasmine JS, Angular JS – Testing of an Angular Service with Mocking

To mock an Angular dependency we must create a mock module with all our mock objects defined through the $provide.value() method. Every time our target will require a particular dependency this will be taken from the mocking module.

In this example we updated our Angular Service Test to use a mock version of the “hangmanConstants” dependency (the original version was just calling the module(“hangmanApp.constants”) method to resolve the real dependency).

Here is our test:

/// <reference path="../../../scripts/jquery-2.1.3.min.js" />
/// <reference path="../../../scripts/jasmine/jasmine.js" />
/// <reference path="../../../scripts/angular.min.js" />
/// <reference path="../../../scripts/angular-mocks.js" />
/// <reference path="../../hangman/hangmanBundle.js" />

describe("Hangman Service - unit:", function () {
    describe("When the service is created", function () {
        var target;
 
        //Mock objects
        var mockHangmanConstants = {
            NS: window.hangman,
            MAX_ERRORS: 5
        };
 
        beforeEach(function () {
 
            //Create a mock module with our
            //mock objects. The dependencies
            //will be all taken from here
            module(function ($provide) {
                $provide.value("hangmanConstants", mockHangmanConstants);
            });
 
            //Load the module to test
            module("hangmanApp.services");
            inject(function ($injector) {
 
                //Resolve the hangmanService
                target = $injector.get("hangmanService");
            });
        });
 
        it("It should return a keyboard", function () {
            var keyboard = target.getKeyboard();
            expect(keyboard).toBeDefined();
        });
 
        it("It should return a phrase", function () {
            var phrase = target.getPhrase("The Test Phrase");
            expect(phrase).toBeDefined();
        });
    });
});

Jasmine JS, Angular JS – Testing of an Angular Service

In this situation we don’t have to manage with templates, as we had to do with the directives, so we just use the $injector.get() method (within the inject() method as always) to ask angular to resolve the service that we want to test for us.

Here is our test:

/// <reference path="../../../scripts/jquery-2.1.3.min.js" />
/// <reference path="../../../scripts/jasmine/jasmine.js" />
/// <reference path="../../../scripts/angular.min.js" />
/// <reference path="../../../scripts/angular-mocks.js" />
/// <reference path="../../hangman/hangmanBundle.js" />

describe("Hangman Service - integration:", function () {
    describe("When the service is created", function () {
        var target;
 
        beforeEach(function () {
 
            //Load the module to test
            //and its dependency modules
            module("hangmanApp.constants");
            module("hangmanApp.services");
            inject(function ($injector) {
 
                //Resolve the hangmanService
                target = $injector.get("hangmanService");
            });
        });
 
        it("It should return a keyboard", function () {
            var keyboard = target.getKeyboard();
            expect(keyboard).toBeDefined();
        });
 
        it("It should return a phrase", function () {
            var phrase = target.getPhrase("The Test Phrase");
            expect(phrase).toBeDefined();
        });
    });
});

Here’s our service’s code

angular.module("hangmanApp.services").service("hangmanService",
    ["hangmanConstants", function (hangmanConstants) {
        var _this = this;
 
        _this.getKeyboard = function () {
            return new hangmanConstants.NS.KeyboardModel();
        };
        _this.getPhrase = function (phrase) {
            return new hangmanConstants.NS.PhraseModel(phrase);
        };
    }]);

Jasmine JS, Angular JS – Testing of an Angular Directive

We need Chutzpah to be able to run this test. Resharper has some problem when loading the directive’s template.

http://blogs.msdn.com/b/matt-harrington/archive/2014/10/27/javascript-unit-testing-using-the-chutzpah-test-runner-in-visual-studio.aspx

With the Chuzpah extensions for VS (Test Runner and Test Adapter) we can see the JS tests within the default Visual Studio Text Explorer and run them from there, in the same way we manage the standard C# tests.

Here is our test:

//References of all the .JS needed for the test
/// <reference path="../../../scripts/jquery-2.1.3.min.js" />
/// <reference path="../../../scripts/jasmine.js" />
/// <reference path="../../../scripts/angular.min.js" />
/// <reference path="../../../scripts/angular-mocks.js" />
/// <reference path="../../app/app.js" />
/// <reference path="../../app/directives/d.personsList.js" />

describe("Directive Persons List:", function () {
    describe("When the directive is compiled", function () {
        var $scope, target;
 
        beforeEach(function () {
 
            //Load the module to test
            module("app.directives");
 
            //The inject() function is used to resolve dependencies.
            //I can then assign the dependencies that I receive here to variables
            //defined outside the function. Is possible as well to use "_" to refer
            //a dependency without overriding the related outer reference.
            //For instance i could refer the $rootScope dependency with the name
            //"_$rootScope_", to assign it to an outer reference named "$rootScope"
            inject(function ($rootScope, $templateCache, $compile) {
 
                //Create the test $scope
                $scope = $rootScope.$new();
                $scope.persons = [
                    { name: "name1", surname: "surname1" },
                    { name: "name2", surname: "surname2" },
                    { name: "name3", surname: "surname3" }
                ];
 
                //Push the directive's template on the
                //template cache (work only with Chutzpah)
                var view = $templateCache.get("/jsApp/app/directives/d.personsList.html");
                if (!view) {
                    $.ajax({
                        type: "GET",
                        async: false,
                        cache: false,
                        url: "../../app/directives/d.personsList.html"
                    })
                    .done(function (data) {
                        view = data;
                    });
                    $templateCache.put("/jsApp/app/directives/d.personsList.html", view);
                }
 
                //Compile the directive using the test $scope
                target = $compile("<persons-list persons='persons'></persons-list>")($scope);
                $scope.$digest();
            });
        });
 
        it("It should be defined", function () {
            expect(target).toBeDefined();
        });
 
        it("It should contain a person list", function () {
            var personList = target.find("li");
            expect(personList.length).toBe(3);
            for (var n = 0; n < 3; n++) {
                expect($(personList[n]).text().trim()).toBe(
                    $scope.persons[n].name + " " + $scope.persons[n].surname);
            }
        });
    });
});

Here is our directive’s code

angular.module("app.directives").directive("personsList", function () {
    return {
        restrint: "E",
        replace: true,
        templateUrl: "/jsApp/app/directives/d.personsList.html",
        scope: {
            persons: "="
        }
    };
});

Here is our directive’s template

<ul>
    <li ng-repeat="person in persons">
        {{person.name}} {{person.surname}}
    </li>
</ul>

Jasmine JS – Introduction

I can add Jasmine JS to a VS solution from NuGet.

Once downloaded Jasmine I can write a simple set of tests (in a dedicated JS file) in the following way:

describe("MyTests", function () {
    it("This is the test 1", function () {
        expect("this is a test string").toContain("test");
    });
    it("This is a test with positive result", function () {
        expect(2 * 4).toEqual(8);
    });
    it("This is a test with negative result", function () {
        expect(2 * 4).toEqual(9);
    });
});

I can run JS tests directly from VS with Resharper (otherwise other extensions are available, such as Chutzpah). With Resharper I can run a single test or the entire test set by right-clicking on the text editor. The results will be displayed in both the text editor and the Resharper Unit Test window.


Executing code before each test

We can use the beforeEach(function() { … }) method to define the code that should be executed before each test.