Before we delve into implementing sorting and searching, I would like to spend some time on unit testing. Unit tests for me are fundamental to any software development endeavor. I’ve been in the trenches long enough to know the importance of writing unit tests. The presence of unit tests also shows discipline & there is a positive correlation between unit tests and code quality. In my opinion writing unit tests is sacrosanct to the discipline of crafting software. Period.

So, now without more ado, let’s see how one can unit test angular code. There is some setup required. First, add a new project of type “Unit Test Project” and name this project NgWebApiGrid.Web.Tests.

Next, install Chutzpah; Chutzpah is a test runner and without this add-on the JavaScript unit tests you write in say Jasmine will not get recognized i.e. they will not appear in the Test explorer. Chutzpah is available as a Visual Studio add-on and to install it go to Tools menu option and then select extensions and updates

NgWebApiGrid24

search for chutzpah and you should see “Chutzpah Test Adapter for the Test explorer” – since I have already installed this add-on my screenshot allows me to disable/uninstall – in your case you will have the option to install the Chutzpah test adapter. Go ahead and install this add-on and make sure to restart Visual StudioNgWebApiGrid25

Now, install Jasmine nuget package by selecting “manage nuget packages for solution” option in the Tools/Nuget package manager option. Search for jasmine and make sure you install the nuget package with id jasmine-js. Again, since I have already installed it there is a checked icon in a green circular box but in your case you should see an install button – go ahead and install JasmineNgWebApiGrid26

You should see the following files appear NgWebApiGrid27

We’ll only be using jasmine.js but let’s just leave the other two files where they are.

Create a folder simpleGridTests and within this folder add a JavaScript file called studentTests.js. We’ll be writing our unit tests here and this folder structure mimics the structure of our app folder where our code that has to be tested lies.

Now that we are setup, let’s write a few unit tests – Jasmine has a global function describe that takes two arguments – the first parameter is a string which is the name of the test suite and the second is a function callback and this is the suite itself. These test suites can be nested, so in our case our skeletal structure of test suites will look as so

describe("student-tests", function () {   

    describe("dataService", function () {
      
     
        
    });


    describe("studentCtrl", function () {

    
    });

});

Here, student-tests is the name of the test suite and tells us that these are student tests. this test suite is further sub-divided into two other test suites; dataService and studentCtrl, these test suites will test the data Service and student controller respectively.

Specs are defined by calling another global function “it” – it also takes two arguments, the first is description of the spec and the second is a function callback which is the spec or test and this has a one or more expectations.

So, here in line 5, a spec has been defined – “can load students” – this should be read as “student-tests dataService can load students”

describe("student-tests", function () {   

    describe("dataService", function () {

      it("can load students", function (dataService) {

      });
        
    });


    describe("studentCtrl", function () {

    
    });

});

Now, we will test if data service can load students – whether the data service can in fact load students is asserted by way of what are known as expectations; so, in line 7, we expect the service to be defined to begin with, and then in line 8 we then expect the count of students to be an empty array (since we still have to retrieve the students) and then once we do retrieve the student data we will expect the student count to be equal to (say) 3 or more than 0 as shown in line 11.

describe("student-tests", function () {   

    describe("dataService", function () {

      it("can load students", function (dataService) {

        expect(dataService).toBeDefined();
        expect(dataService.students).toEqual([]);
        
        //make the call to get student data
        expect(dataService.students.length).toEqual(3);
        

      });
        
    });


    describe("studentCtrl", function () {

    
    });

});

Let’s go about replacing the commented code in line 10 with a service call. One thing to note here is that the service call is a get request that our code makes to the web api. But here we will not be making the actual call rather we will mock the http get request. The notion of mocking is quite common in unit testing. So, what we will do is simulate the behavior of the http get request by using the $httpBackend that Angular provides.

The $httpBackend will be setup as so

describe("student-tests", function () {
    
    beforeEach(function () {

        module('ngWebApiGrid.student');
    });

    var $httpBackend;

    beforeEach(inject(function ($injector) {

        $httpBackend = $injector.get("$httpBackend");

        $httpBackend.when("GET", "api/StudentsApi?currentPage=1&recordsPerPage=5")
            .respond({
                "students": [{ "id": 1, "lastName": "Alexander", "firstMidName": "Carson", "enrollmentDate":  "2005-09-01T00:00:00", "enrollments": null },
                    { "id": 2, "lastName": "Alonso", "firstMidName": "Meredith", "enrollmentDate": "2002-09-01T00:00:00", "enrollments": null },
                    { "id": 3, "lastName": "Anand", "firstMidName": "Arturo", "enrollmentDate": "2003-09-01T00:00:00", "enrollments": null },
                    { "id": 4, "lastName": "Barzdukas", "firstMidName": "Gytis", "enrollmentDate": "2002-09-01T00:00:00", "enrollments": null },
                    { "id": 5, "lastName": "Li", "firstMidName": "Yan", "enrollmentDate": "2002-09-01T00:00:00", "enrollments": null }], "recordCount": 32
            }
            );
    }));


    afterEach(function () {

        $httpBackend.verifyNoOutstandingExpectation();
        $httpBackend.verifyNoOutstandingRequest();
    });


In line 5 above we instantiate our module and then line 12 a call to $injector.get is made which returns an instance of the $httpBackend service & in line 14 we tell the $httpBackend service to return an array of five student object when a get request is made to api/StudentsApi?currentPage=1&recordsPerPage=5

The completed unit tests for the dataService are shown below


describe("student-tests", function () {
    
    beforeEach(function () {

        module('ngWebApiGrid.student');
    });

    var $httpBackend;

    beforeEach(inject(function ($injector) {

        $httpBackend = $injector.get("$httpBackend");

        $httpBackend.when("GET", "api/StudentsApi?currentPage=1&recordsPerPage=5")
            .respond({
                "students": [{ "id": 1, "lastName": "Alexander", "firstMidName": "Carson", "enrollmentDate": "2005-09-01T00:00:00", "enrollments": null },
                    { "id": 2, "lastName": "Alonso", "firstMidName": "Meredith", "enrollmentDate": "2002-09-01T00:00:00", "enrollments": null },
                    { "id": 3, "lastName": "Anand", "firstMidName": "Arturo", "enrollmentDate": "2003-09-01T00:00:00", "enrollments": null },
                    { "id": 4, "lastName": "Barzdukas", "firstMidName": "Gytis", "enrollmentDate": "2002-09-01T00:00:00", "enrollments": null },
                    { "id": 5, "lastName": "Li", "firstMidName": "Yan", "enrollmentDate": "2002-09-01T00:00:00", "enrollments": null }], "recordCount": 32
            }
            );
    }));


    afterEach(function () {

        $httpBackend.verifyNoOutstandingExpectation();
        $httpBackend.verifyNoOutstandingRequest();
    });

    describe("dataService", function () {

        it("can load students", inject(function (dataService) {

            expect(dataService).toBeDefined();
            expect(dataService.students).toEqual([]);

            var options = {
                currentPage: 1,
                recordsPerPage: 5,
            };

            $httpBackend.expectGET("api/StudentsApi?currentPage=1&recordsPerPage=5");
            dataService.getStudents(options);
            $httpBackend.flush();
            expect(dataService.students.length).toEqual(5);

        }));
    });

});

And, the entire set of test suites are shown below & these include the student controller test suite


describe("student-tests", function () {
    
    beforeEach(function () {

        module('ngWebApiGrid.student');
    });

    var $httpBackend;

    beforeEach(inject(function ($injector) {

        $httpBackend = $injector.get("$httpBackend");

        $httpBackend.when("GET", "api/StudentsApi?currentPage=1&recordsPerPage=5")
            .respond({
                "students": [{ "id": 1, "lastName": "Alexander", "firstMidName": "Carson", "enrollmentDate": "2005-09-01T00:00:00", "enrollments": null },
                    { "id": 2, "lastName": "Alonso", "firstMidName": "Meredith", "enrollmentDate": "2002-09-01T00:00:00", "enrollments": null },
                    { "id": 3, "lastName": "Anand", "firstMidName": "Arturo", "enrollmentDate": "2003-09-01T00:00:00", "enrollments": null },
                    { "id": 4, "lastName": "Barzdukas", "firstMidName": "Gytis", "enrollmentDate": "2002-09-01T00:00:00", "enrollments": null },
                    { "id": 5, "lastName": "Li", "firstMidName": "Yan", "enrollmentDate": "2002-09-01T00:00:00", "enrollments": null }], "recordCount": 32
            }
            );
    }));


    afterEach(function () {

        $httpBackend.verifyNoOutstandingExpectation();
        $httpBackend.verifyNoOutstandingRequest();
    });

    describe("dataService", function () {

        it("can load students", inject(function (dataService) {

            expect(dataService).toBeDefined();
            expect(dataService.students).toEqual([]);

            var options = {
                currentPage: 1,
                recordsPerPage: 5,
            };

            $httpBackend.expectGET("api/StudentsApi?currentPage=1&recordsPerPage=5");
            dataService.getStudents(options);
            $httpBackend.flush();
            expect(dataService.students.length).toEqual(5);

        }));
    });


    describe("studentCtrl", function () {

        it("loads data", inject(function ($controller, $rootScope) {

            var theScope = $rootScope.$new();

            $httpBackend.expectGET("api/StudentsApi?currentPage=1&recordsPerPage=5");

            var ctrl = $controller("studentCtrl", {
                $scope: theScope,

            });

            $httpBackend.flush();

            expect(ctrl).not.toBeNull();
            expect(theScope.data).toBeDefined();
            expect(theScope.currentPage).toBeDefined();
            expect(theScope.totalItems).toBeDefined();
            expect(theScope.maxSize).toBeDefined();
            expect(theScope.recordsPerPage).toBeDefined();
            expect(theScope.numberOfPageButtons).toBeDefined();

        }));

        it("studentCtrl should call dataService", inject(function ($controller, $rootScope, dataService) {

            var theScope = $rootScope.$new();

            $httpBackend.expectGET("api/StudentsApi?currentPage=1&recordsPerPage=5");

            var ctrl = $controller("studentCtrl", {
                $scope: theScope,
                dataService: dataService,
            });

            $httpBackend.flush();

            expect(theScope.totalItems).toEqual(32);
            expect(theScope.data.length).toEqual(5);

        }));

    });

});

Once you run all your unit tests, all your unit tests should pass and your test explorer should look like so

NgWebApiGrid28

Source is here and you can see the changes/additions I made in this blog by looking at this commit.

After this quick detour, in my next post I’ll implement the ability to sort.

Advertisements