This chapter focuses on the troubleshooting libraries and tools for AngularJS. In this chapter, we will also discuss unit testing of the AngularJS application using Jasmine and Karma. At the end of this chapter, we will go through the AngularJS coding best practices. This chapter is organized as follows:
JavaScript is a dynamic computer programming language. It is mostly used for client-side application development and as part of web browsers. JavaScript communicates asynchronously with clients, such as web browsers and it also controls the client. JavaScript can be used with server-side programming with the runtime environment, for example, Node.js and AngularJS.
JavaScript is categorized as a prototype-based scripting language using dynamic typing. Prototype-based programming is an elegant object-oriented programming in which we can reuse behavior, which is known as inheritance. JavaScript can also be used in other environments along with web browsers, such as PDF documents, site-specific browsers, and desktop widgets.
JavaScript is a typed language and has great power of manifestation, but there is no help from the compiler for JavaScript. The JavaScript code is executed through interpretation instead of compilation. The AngularJS framework provides many features to test the application created using the AngularJS framework. There are also a few libraries and tools available to test the applications created with the AngularJS framework. For example, Jasmine, Karma, Grunt, Bower, and Yeoman, explained as follows:
In computer programming or application development, unit testing is a technique by which distinct units of applications are tested to determine whether they are working as expected, according to the user's requirement. Preferably, each unit test is sovereign from each other. Replacements for unit test, such as functions, objects, and test data, which are configured to test a unit by running it under every condition and monitoring its behavior and output, are known as test harness. For example, to test a function, we need to verify the given set of inputs, we have to conclude whether the function is returning the proper output values, and will elegantly handle failures during the execution. Unit test is usually written and tested by developers to confirm that the code meets its design and behaves as envisioned.
The goal of unit testing is to ensure that each part of the application is correct. A unit test delivers a firm written contract that the piece of code must satisfy. As a result, it gives numerous benefits, which are described as follows:
Unit testing is ought to be an exhausted conjunction with different libraries or tools, such as Jasmine and Karma. They will solely show the prevalence or absence of specific errors; they can't prove a whole absence of errors. So as to ensure correct behavior for each execution path and each attainable input, and to make sure that errors are absent, different technique's area units are needed, particularly to apply formal strategies to prove that a library and tool part has no sudden behavior.
The AngularJS framework has become one of most popular frameworks to develop single-page applications. The AngularJS framework's unique approach to HTML compiling and two-way data binding makes it an operative tool to develop client-side applications competently. The AngularJS framework is defined by modules, such as directives, controllers, services, filters, and factories. Each module in the AngularJS framework is able to communicate with another through its external interface. All of these modules also share a common attribute. These modules behave as black boxes, which means that these modules have an inner behavior and outer interface materialized by input and outputs. This is precisely what unit tests are for: to test a module's outer interfaces.
The following points show what and what not to test:
true
and other with an outcome of false
.In the AngularJS framework, several types of objects can be declared in modules, such as filters, services, factories, and so on. In this section, we will discuss how these object types can be bootstrapped and tested in AngularJS.
AngularJS's filters, services, and factories are easy to test because they need very few preparations, which are usually other services. AngularJS links services to other services or objects using the very communicative reliable injection model, which is basically the same as asking a function's parameters. The way of injecting things in test cases in AngularJS is super easy and simple. The following code shows how you can test the AngularJS object factory:
var app = angular.module('myApp', []) app.factory('myFactory', ['$log', function($log) { return { warning: function() { $log.warn('Warning message'), } }; }]);
Factory test unit code is as follows:
describe('myApp', function() { beforeEach(module('myApp')); var Factory; var $log; beforeEach(inject(function(_myFactory _, _$log_) { Factory = _ myFactory_; $log = _$log_; sinon.stub($log, 'warn', function() {}); })); describe('when invoked', function() { beforeEach(function() { Factory.ook(); }); it('should say Ook', function() { expect($log.warn.callCount).to.equal(1); expect($log.warn.args[0][0]).to.equal('Ook.'), }); }); });
As seen in the preceding code, the $log
service is injected into the factory called myFactory
. In the AngularJS framework, the $log
service is a service to log. It writes the message into the client browser's console. The main advantage of these services is to perform debugging and troubleshooting of AngularJS applications.
The AngularJS $log
service has the following methods:
log
: This method writes the log messageinfo
: This method writes an information messagewarn
: This method writes a warning messageerror
: This method writes an error messagedebug
: This method writes a debug messageThe following code shows the use of the $log
services in the AngularJS controller:
var app=angular.module('logExample', []); app.controller('ctrlLog', function($scope, $log) { $scope.$log = $log; $scope.message = 'Hello World!'; });
The following is the DOM implementation:
<div ng-controller="LogController"> <p>Reload this page with open console, enter text and hit the log button...</p> Message: <input type="text" ng-model="message"/> <button ng-click="$log.log(message)">log</button> <button ng-click="$log.warn(message)">warn</button> <button ng-click="$log.info(message)">info</button> <button ng-click="$log.error(message)">error</button> </div>
The following screenshot shows the output of the DOM:
As shown in the preceding screenshot, by clicking on the button, you will see the following in the browser's console:
In the preceding screenshot, we used the Internet Explorer browser, and by clicking on the F12 key, open the develop tool of the browser, and then click on Console. By clicking on each button on the page, we will get the previous output:
An AngularJS directive is basically a function that executes once the AngularJS compiler finds it within the DOM. The function(s) will do nearly anything, which is why it is hard to define what a directive is. In other words, an AngularJS directive is a new way to encapsulate the HTML in the DOM. Every directive contains a name (such as ng-repeat
, tabs, your-own-directive) and every directive determines where it is often used, such as in the DOM element, attribute, or class.
In the following code, a directive displays text and a button inside the div
element:
var app = angular.module('myApp', []) app.directive('myUnitTestDirective', function() { return { scope: {label: '=', callback: '&onClick'}, replace: true, restrict: 'E', link: function(scope, element, attrs) { }, template: '<div>' + '<div>{{label}}</div>' + '<button ng-click="callback()">Click me!</button>' + '</div>' }; });
For the preceding code, we would like to test whether the HTML element label has properly passed to the div
element and to verify that the click event is fired when the button is clicked.
The following is the directive unit test code:
describe('directives', function() { beforeEach(module('myApp')); var element; var outerScope; var innerScope; beforeEach(inject(function($rootScope, $compile) { element = angular.element('<super-button label="myLabel" on- click="myCallback()"></super-button>'), outerScope = $rootScope; $compile(element)(outerScope); innerScope = element.isolateScope(); outerScope.$digest(); })); describe('label', function() { beforeEach(function() { outerScope.$apply(function() { outerScope.myLabel = "Hello world."; }); }) it('should be rendered', function() { expect(element[0].children[0].innerHTML).to.equal('Hello world.'), }); }); describe('click callback', function() { var mySpy; beforeEach(function() { mySpy = sinon.spy(); outerScope.$apply(function() { outerScope.myCallback = mySpy; }); }); describe('when the directive is clicked', function() { beforeEach(function() { var event = document.createEvent("MouseEvent"); event.initMouseEvent("click", true, true); element[0].children[1].dispatchEvent(event); }); it('should be called', function() { expect(mySpy.callCount).to.equal(1); }); }); }); });
In the preceding code, when we click on the Click me! button, the function is passed as the on-click attributes are called. If we look closely at the directive, we can find out that the function gets locally renamed to callback. It will be published under this name on the directive's isolated scope.