This post is in continuation of my previous post on Unit Testing in AngularJS where we discussed about the basics of Unit Test in AngularJS, setting up the infrastructure and then wrote couple of unit tests. First we started with test for plain JavaScript method then we wrote an angular application and added unit test for Angular controller. While writing unit test, we also learned basics of Jasmine framework. The link of my previous post is given below
Getting started with Unit Testing in {{AngularJS}} – Part 19
Today we will write unit tests for following components.
- Angular Service
- Custom Filters
- Custom Directive
Note – This post is a part of series of posts on AngularJS and it is 20th one. Click here to view all the post. Apart from that you can also use the navigation links for next and previous post in each post at the bottom to check all.
Testing your Service
As we know that service is an independent object which does some specific work and can be reused at multiple places. We normally used to have many services in our application which does various tasks. These services are the first candidates which should be considered for writing unit tests. Broadly in our service, we do two type of tasks, first where we take some input, write some logic and return the output accordingly. Second, where we connect some third party services via AJAX, process the response and return it. First type of service can be tested easily as normal JavaScript function. We are going to write Unit Test for second type.
We will extend our previous application where we hard coded the values in Angular Controller. Now instead, we will be creating an Angular service which will get the data from server and return that. Let’s see our service
homeModule.factory("TalksService", function ($http, $q) { return { getTalks: function () { // Get the deferred object var deferred = $q.defer(); // Initiates the AJAX call $http({ method: 'GET', url: '/home/GetTalkDetails' }).success(deferred.resolve).error(deferred.reject); // Returns the promise - Contains result once request completes return deferred.promise; } } });
Note – In this series of posts on AngularJS, I have written following post where we discussed about Angular Services. To learn more about services, you can refer that
Learning {{AngularJS}} with ASP.NET MVC – Part 4
We have added our service using Factory which uses $http service to get the data from the server. One of the key points is that we are returning a promise here. Now let’s make the required changes in the controller
After these changes, our application would run same as we are now returning the same data server via Web API. Now it’s time to write the Unit Test
Writing Unit Test
There are two new things here, usage of $http Service and returning promise. To test $http service, AngularJS provides a Fake implementation as $httpBackend which helps in mocking the service and setting up the response. To write the test, we need to initialize TalkService and httpBackend that we can inject at before each. Also we need to initialize homeModule as
var TalksServiceFactory, httpBackend; beforeEach(module("homeModule")); beforeEach(inject(function($httpBackend, TalksService) { httpBackend = $httpBackend; TalksServiceFactory = TalksService; }));
And unit test
it("Should Return four Talks", function () { var talks; // Setting the mock up mock http response httpBackend .expect('GET', '/home/GetTalkDetails') .respond(200, [ { id: '1001', name: 'Real Time Web Applications with SignalR', speaker: 'Brij Bhushan Mishra', venue: 'Hall 1', duration: '45' }, { id: '1002', name: 'Power of Node.js', speaker: 'Dhananjay Kumar', venue: 'Hall 2', duration: '75' }, { id: '1003', name: 'Getting started with AngularJS', speaker: 'Brij Bhushan Mishra', venue: 'Hall 1', duration: '60' }, { id: '1004', name: 'Microsoft Azure - Your cloud destination', speaker: 'Gaurav mantri', venue: 'Hall 1', duration: '45' } ]); // calling service TalksServiceFactory.getTalks().then(function (response) { talks = response; }); // Flushing httpBackend httpBackend.flush(); // verification expect(talks.length).toBe(4); });
Above code is self-explanatory. First we are initializing the mock service, calling the service and finally verifying the response. We can configure httpBackend for different scenarios based on usage of $http in actual service.
Custom Filter
Filter is another one of the most used features of AngularJS. Here we are going to use one custom filter that we wrote in one of previous post where we discussed about Custom Filters. So let’s quickly see the Filter first
homeModule.filter('ConvertoPhone', function () { return function (item) { var temp = ("" + item).replace(/\D/g, ''); var temparr = temp.match(/^(\d{3})(\d{3})(\d{4})$/); return (!temparr) ? null : "(" + temparr[1] + ") " + temparr[2] + "-" + temparr[3]; }; });
Note – In this series of posts on AngularJS, I have written following posts where we discussed about Filters and writing custom one. To learn more about it, you can refer the following links
- Exploring Filters in {{AngularJS}} – Part 13
- Exploring Filters in {{AngularJS}} Contd. : Custom Filters – Part 14
- How to use Angular Filter in Controller, Factory, Service – Part 15
Now to write unit test, we need to inject the $filter and then instantiate our custom filter in the init test. Let’s see our unit test
describe("Filter Tests ->;", function () { var filter; beforeEach(module('homeModule')); beforeEach(inject(function (_$filter_) { filter = _$filter_; })); it('if the number formatted', function () { var phoneFilter = filter('ConvertoPhone'); expect(phoneFilter('1234567891')).toEqual('(123) 456-7891'); }); });
Custom Directive
Directives are again one of the most important components for AngularJS. Writing Custom Directive is a complex task because it is not just another function which can be injected and called from anywhere. Custom Directives are declaratively used in HTML. As it directly changes the view and also designed in a way to be reused at different views, provided the scope is properly isolated based on requirement, these should be properly tested.
We are going to write two Custom Directives : First would be a simple one and another using isolate scope and we will write unit test for both the cases. First directive is an element directive which reads some information from scope and replaces the directive with the provide html in directive as
homeModule.directive('myelementdirective', function () { var directive = {}; directive.restrict = 'E'; //restrict this directive to elements directive.template = "Hello {{name}} !! Welcome to this Angular App"; return directive; });
Note – In this series of posts on AngularJS, I have written following posts where we discussed about writing custom directives. To learn more about it, you can refer the following links
- Creating custom directive in AngularJS – Part 9
- Create Custom Directive using Isolate scope in AngularJS – Part 10
Writing Unit Test
Writing unit test is tricky for directives because every custom directive is first complied which renders the html then other actions like binding, any user actions are performed. There is a digest cycle which runs and responsible for any binding or any other initialization before a directive appears on page. So when writing unit test we ourself need to compile the directive using compile service and create a specific scope if required. Then run the digest cycle to make it in similar state as on UI. For that we need to initialize the scope, compile service by injecting it using beforeEach. Now let’s see our test
var compileService, rootScope; beforeEach(module('homeModule')); // Store references to $compile and $rootScope so they can // be uses in all tests in this describe block beforeEach(inject(function (_$compile_, _$rootScope_) { compileService = _$compile_; rootScope = _$rootScope_; rootScope.talk = { name: 'abc', duration: '25m' }; rootScope.name = 'Brij' ; })) it('My element Custom Directive defined', function () { var compiledDirective = compileService(angular.element('<myelementdirective/>'))(rootScope); rootScope.$digest(); expect(compiledDirective).toBeDefined(); });
Here we are compiling the directive and running the digest cycle and checking whether it is defines. Then we can write another test which checks whether the correct html is rendered or not as
it('My element Custom Directive renders proper html', function () { var compiledDirective = compileService(angular.element('<myelementdirective/>'))(rootScope); rootScope.$digest(); expect(compiledDirective.html()).toContain("Hello Brij !! Welcome to this Angular App"); }); Angular App"); });
Testing Custom Directive with isolated scope
Now we are going to write another directive with isolate scope. If you know or referred my previous post then we find that three types of isolated scope are available in Custom Directives which is also known as Local scope properties. We are going to write two way binding scope where the data is always in sync with parent regardless where it is getting changed. So let’s see the custom directive first
homeModule.directive('bindcustomdirective', function () { var directive = { restrict: 'E', // restrict this directive to elements scope: { talkinfo: '=' }, template: "<input type='text' ng-model='talkinfo.name'/>" + " <div>{{talkinfo.name}} : {{talkinfo.duration}}</div> ", }; return directive; });
Here talkinfo gets initialize with the scope passed via an attribute while using Directive as
<bindcustomdirective talkdetails="talk" />
As in the template, we have input which allows to change the scope object, this reflects in the parent scope as well.
Writing Unit Test
To write the unit test, most of things would be same as above like initialization of compiler service, scope and assign some initial value to talk object in parent scope. So lets move to the test itself
it('Bind Custom Directive defined', function () { var compiledDirective = compileService(angular.element(' <bindcustomdirective talkinfo="talk" />'))(rootScope); rootScope.$digest(); var isolatedScope = compiledDirective.isolateScope(); expect(isolatedScope.talkinfo).toBeDefined(); });
Here we got the compiled directive using compiler service and run the digest cycle same as earlier one. One extra line added to get the isolate scope from the compiled directive and checking whether talkInfo is defined.
We will write another test and here we will check that if we change the isolated object’s property whether that get reflected in parent scope or not as
it('Bind Custom Directive two way binding check', function () { var compiledDirective = compileService(angular.element(' <bindcustomdirective talkinfo="talk" />'))(rootScope); rootScope.$digest(); compiledDirective.isolateScope().talkinfo.name = "Building modern web apps with ASP.NET2"; expect(rootScope.talk.name).toEqual("Building modern web apps with ASP.NET2"); });
Conclusion
We have written the unit tests few very important components of AngularJS. Although many more test could be written and even these components vary based on requirement, accordingly different unit test may be required. But in this post, I tried to provide the basics of writing unit test for these test. Do share your feedback or face any difficulty for writing unit test for any specific component. I will try to answer that.
Cheers,
Brij