Angular 1 TDD – Creating a service

In this blogpost, I will explain how to create an Angular service using TDD and show how it affects the code coverage report. This post is a collection of various resources on the web in addition to my personal experience.

Prerequisites

This blogpost assumes the following has been installed on your machine.

  • Visual Studio 2015 Update 3 (I used the enterprise edition)
  • Node.js tools for Visual Studio (http://bit.ly/2iDAnXK)
  • Node.js (http://bit.ly/1Wdc3FQ)
  • Git (http://bit.ly/1cIHx4Y)
  • A Visual Studio project with the following libraries (see the previous post on how to enable this):
    • Gulp (Task runner)
    • Bower (web package manager)
    • ngMock (AngularJS module relating to unit testing)
    • BardJS (library to avoid certain repetitive testing code)
    • AngularJS (MVW JavaScript framework)
    • Chai (TDD/BDD assertion library)
    • Mocha (JavaScript test framework)
    • Karma (JavaScript test runner by the AngularJS team)

Result

The following will be in place at the end of this post:

  • Service unit tests
  • A service
  • A code coverage report

Creating the unit test

Before actually creating the service, we will add the unit test first, since we are doing TDD. In this post, we will use the freely available game database from IGDB.com. I have used mashape, which contains a marketplace where various APIs are distributed in order to consume. The IGDB.com API only exposes GET methods, which is fine for now.

Let’s create the test:

Figure 1: game database service test file.

I have chosen to follow the Angular 1 style guide published by John Papa. You can use whatever naming and structure you like as long as it is consistent and you are comfortable in finding back your code.

Now let’s write some code.

  1. describe(‘game database service’,  function()  {
  2.     var  gameDatabaseService;
  3.     beforeEach(function()  {
  4.         module(‘gameApp’);
  5.         bard.inject(this,  ‘GameDatabaseService’);
  6.         gameDatabaseService  =  GameDatabaseService;
  7.     })
  8. it(‘should return data’,  function()  {
  9.         var  data  =  gameDatabaseService.retrieveData();
  10.         expect(data).to.not.be.undefined;
  11.     });
  12. });

Without going into too much detail about every line of code, I would like to point out the simplicity of using BardJS to inject Angular application components (in this case the GameDatabaseService) after which the variable gameDatabaseService can be immediately used by the first test.

If we run our test it fails:

Figure 2: Our first failed test.

This is as expected, since both the service and the module have not been created yet.

Creating the service

After having created the first failing test, we have to ask ourselves what the minimal requirements are to let it pass. This approach should lead to an increase in code quality and decreasing the time in doing so.

So, let’s start creating the service:

Figure 3: The game database service file.

I believe it is good practice to put the source code files next to the unit tests for the reasons specified in the Angular 1 style guide.

The code of the game database service could contain something like the following:

  1. (function() {
  2.     ‘use strict’;
  3.     angular.module(‘gameApp’, []).service(‘GameDatabaseService’, GameDatabaseService);
  4.     function GameDatabaseService() {
  5.         var service = {
  6.             retrieveData: retrieveData
  7.         };
  8.         function retrieveData() {
  9.             return {
  10.                 “value”: “some data”
  11.             };
  12.         }
  13.         return service;
  14.     };
  15. })();

The unit test passes of course, but this method will always return the same data.

Figure 4: Unit test passes, but some work left.

A better unit test

The previous test succeeded, but it was not that hard to make it succeed and we could say the code in the service is of poor quality. Since we are doing TDD, we could also say that the unit test is of poor quality and that we need to ask ourselves what we actually want to test.

Ideally, we want to test the following:

  1. A GET request is being made to the IGDB.com endpoint
  2. The return data matches the expected data.

Mocking the GET request and matching the returned data

A good explanation of ngMock and using the $httpBackend service is given here.

Without further ado let’s see how we can test that a GET request is being made.

  1. it(‘should return correct data from IGDB.com after an authenticated request has been made’, function() {
  2.     var responseData;
  3.     var isAuthHeaderPresent = function(headers) {
  4.         return headers[‘X-Mashape-Key’];
  5.     };
  6.     $httpBackend.whenGET(‘https://igdbcom-internet-game-database-v1.p.mashape.com/characters/?fields=*&limit=1’, isAuthHeaderPresent).respond(200, gameData);
  7.     gameDatabaseService.retrieveData().then(function(data) {
  8.         responseData = data;
  9.     });
  10.     $httpBackend.flush();
  11.     expect(responseData).to.deep.equal(gameData);
  12. });

When making requests to the mashape endpoint a consumer always needs to supply a unique authentication key. This is supplied in the request header. This test explicitly states that the authentication header must be present in the request.

We could go one step further by matching the key by its value, but I don’t believe this would contribute to the quality of the code. To me it would make sense to draw a line here and state that testing the process of validating keys is part of end to end testing. This is because authentication is something that the exposed API provides, not the service.

The deep equal syntax has been a life saver to me multiple times. It is a very useful function that compares the contents of two arrays. For more information about deep equal see the documentation on ChaiJS.

This new test will fail as expected:

Figure 5: Better test fails.

So, let’s write the code to make it pass:

  1. function retrieveData() {
  2.     var deferred = $q.defer();
  3.     $http({
  4.         method: ‘GET’,
  5.         url: ‘https://igdbcom-internet-game-database-v1.p.mashape.com/characters/?fields=*&limit=1’,
  6.         headers: {
  7.             ‘X-Mashape-Key’: ‘test’
  8.         }
  9.     }).then(function successCallback(response) {
  10.         deferred.resolve(response.data);
  11.     }, function errorCallback(response) {
  12.         deferred.reject();
  13.     });
  14.     return deferred.promise;
  15. }

We are using the Angular promise compliant implementation ($q), the GET request is asynchronous.

In a real-world scenario, the service would not return data, because the authentication key is invalid. This is something that can be tested using ngMockE2E, but is left out of scope for now.

And here we have it, both the first and second test pass:

Figure 6: Passing tests using $httpBackend.

 

Code coverage

What would be expected if we open our code coverage report?

At the moment, we are testing a happy flow, meaning that we have written a unit test to return data successfully. Knowing this, we could say that all code contributing to returning the data successfully in our game database service will be traversed.

Let’s open the code coverage report:

Figure 7: Location of the code coverage report.

Figure 8: Code coverage index.html.

Figure 9: game-database.service.js code coverage report.

As we expected, all code is traversed, leaving the erroneous request untraversed. The unit test for the erroneous request looks as follows.

  1. it(‘should return an error after an erroneous request has been made’, function() {
  2.     var error;
  3.     var errorMessage = ‘Erroneous request’;
  4.     var isAuthHeaderPresent = function(headers) {
  5.         return headers[‘X-Mashape-Key’];
  6.     };
  7.     $httpBackend.whenGET(‘https://igdbcom-internet-game-database-v1.p.mashape.com/characters/?fields=*&limit=1’, isAuthHeaderPresent).respond(401, errorMessage);
  8.     gameDatabaseService.retrieveData().catch(function(e) {
  9.         error = e;
  10.     });
  11.     $httpBackend.flush();
  12.     expect(error).to.equal(errorMessage);
  13. });

Notice that the same legitimate header is used as in the previous test. This is because the retrieveData method needs that in order to proceed with the request. The main difference is that the $httpBackend returns a failure when this request is being made. The updated reject code in the game-database.service.js file is as follows:

  1. function errorCallback(response) {
  2. deferred.reject(response.data);
  3. })

When we refresh the code coverage report we see that the reject code has been traversed.

Figure 10: 100% coverage report.

Writing unit tests of high quality is far more important than achieving high code coverage. I would advise to think carefully about what you are testing and why you aren’t testing certain uncovered paths. Don’t write a unit test blindly just to cover some code, but try to find a balance. A nice short article about the usefulness of code coverage can be found here.

Summary

We have created a unit test for the game database service. Next, we wrote the service that is responsible for retrieving game data from an API endpoint.

We also generated a code coverage report and saw how the unit tests influence the report. In next posts, we will continue our TDD approach and wire up the game database service to a controller and a view.

Leave a Reply