AngularJS Unit Testing and service mocking

Here we are focusing basically on Karma and Jasmine. Karma is the Test runner for your JavaScript Unit tests. Jasmine is the behavior driven development framework for JavaScript.

Testing is one of the main aspect in software  development and it is more of a vital concern when you follow a practice such as TDD (Test driven development). Developing an AngularJS application can also be done using a TDD approach and it can be implemented in such a way that all the components, services are tested properly as Units.

AngularJS has some utility accessories when it comes to testing. Here we are focusing basically on karma and Jasmine. Karma is the Test runner for your javascript Unit tests. So it can be called the executor. It is a  command line tool that used to spawn a web server which loads your application's source code and executes your tests. There are several configurations that entails with it where we can configure Karma to run against a number of browsers. But most of the time developers use it with PhantomJS which is a Headless browser. This is very famous among developers. Syntaxes we use to test Js code is known as Jasmine.

Jasmine is a behavior driven development framework for JavaScript. It comes with lot of  functions to help with structuring our tests and also making assertions. It also allows us to group things together and maintain the Test code in a well organized manner. Which is an important aspect when it comes to maintaining a code base.

So above is a basic introduction on the Testing with AngularJS, now we can dig into the real world problem that we encountered while improving the coverage of a Js code base we had.

Scenario

Actual requirement we had was to display some text label value in a partial. This template contains a list of items where it’s display tag can be either “All Sections” or the section code name. This text label denoted whether the item we are listing belongs to all sections or to a specified section.  And this text label is a localized one, so based on the language it should display different localized text label. For an instance consider the below set of items on a template.

Item Display Tag
Clothes clothes_section_code
Books books_section_code
Food all_sections
Drinks drinks_section_code

Above items set has their display tags listed along with them, see that item “Food” is marked with tag “all_section” so this is based on some business logic we have within the application. And it is bit complicated. It uses some logic based on the Item’s attributes. If the given item is not having a section code  it should display the relevant tag name as “all_sections” . So all this display tag population was given as a single unit within the code. Then we wanted to test that Unit , to make sure we are following the logic correctly with some Unit tests. Then we started implementing this test case with AngularJS test cases. As i mentioned earlier this display tag needs to be a localized message (i18N support). So we have used some localization service that we implemented using a third party library to get this localized text label. But this part of the code is not our business logic , it is some third party service we are using hence we do not need to test that part but the business logic of populating the tag code name. At this kind of scenarios we can use AngularJS service mocking mechanism to mock the services that we do not need to test but needed for the testing as utilities to the main concern we have.

Then let's dig more into the code we have come up with, for Jasmine test cases we can always integrate same kind of test executions to a single test suit. See the below snippet.

describe('Testing  the list of items', function() {
  it('Test the population of tag name when item is null ', function() {
    // your test assertion goes here
  });
});

So we start with describe, where we denote that we need to group our test cases within this as a single Test suit. And each and every test case is denoted with it(); within that you can execute all your assertions to verify business logic.

Then when you execute each and every test cases within the file, you might need to use some services again and again. So for this we have beforeEach() with us.

Also we would like to inject a reference once, in a beforeEach() block and reuse this in multiple it() clauses. For this we can always assign a reference to a variable that is declared in the scope of the describe() block. So when defining this variable we most of the time tend to use the same name as the service name. For an instance if we have a service called itemService we will just declare it in the describe block and when it comes to get it injected to the code we have inject() function. So the injected parameters can be enclosed with underscores. These are ignored by the injector when the reference name is resolved. For example we have  the parameter itemService which would be resolved as the reference itemService . Since it is available in the function body as itemService, we can then assign it to a variable defined in an outer scope. So then we can use our itemService reference from all the test cases we have.

var ngControllers, rootScope, itemService ;
 
    beforeEach(function () {
        module(‘MyApp’);
        inject(function (_$rootScope_, _$controller_, _itemService_) {
            rootScope = _$rootScope_;
            ngControllers = _$controller_;
            itemService = _itemService_;
            };
        });
    });

Now consider the below much simplified  snippet where we populate the item tag name in one of our controller.

$scope.getTagDisplayName = function (itemToShow){
    var tagName= itemToShow.getDisplayName();
    if(tagName){
        return tagName;
    } else {
      return translationService.getMessageText("all_sections");
    }
};

So in the above code snippet it uses a TranslationService as said before. So we do not need to test that but mock it . So we can use the same beforeEach() as earlier and create a mock for the above.

var ngControllers, rootScope, itemService, translationService  ;
 
    beforeEach(function () {
        module(‘MyApp’);
        inject(function (_$rootScope_, _$controller_, _itemService_) {
            rootScope = _$rootScope_;
            ngControllers = _$controller_;
            itemService = _itemService_;
            translationService = {
                getMessageLabel: function () { // mocked reply.
                    return "All Sections";
                }
            };
 
            };
        });
    });

Now see that we are using a mocked snippet above in the BeforeEach, so when you call getMessageLabel(), it returns a constant text message. Now we can use the mocked service in our test cases as below.

makeController(scope);//utility function to make the controller for //testing.
expect(scope.getTagDisplayName(item)).toBe('All Sections');
 
var makeController = function ( scope) {
   
        var cntl = ngControllers('ItemModalController', {$scope: scope,
            ItemService: itemService, TranslationService: translationService});
 
        return cntl;
}

See above when making the controller we are injecting the mocked TranslationService reference for the controller.  itemService is also injected here , the one we loaded at the beforeEach.

Now note that whenever you call the TranslationService ‘s getMessageLabel() it is supposed to return “All Sections” as the text label. So it is a mocked reply for us. So this is a simple way of mocking required AngularJs services in Test coding. Likewise you can mock any number of services in your actual implementation and inject them to be used in the Test code.