Unit testing AngularJS directives
As we are currently working on marketing our load testing solution we don't get much time left for coding. We identified some issues that could spoil user experience. It could make us loose some prospects.
So I took one day off backlinking / mailing / phoning / marketing to return to my beloved IDE. One day of coding in six weeks, it feels like holidays! I took the opportunity to improve our frontend SonarQube metrics. We now have only 5 hours left of technical debt. Not as well as the backend code quality, but it is on the right track:
I fixed a large part of the issues and started to unit tests our AngularJS directives using Karma.
A first try with AngularJS directives and Karma¶
A simple directive¶
In OctoPerf we use a simple directive to format error messages:
app.directive('appErrorMessage', function() {
return {
restrict: 'EA',
scope: {
errorMessage : '='
},
templateUrl:'app/shared/directives/tools/app_error_message.html'
};
});
The following code would display the next image if the signinFormController controller captures an error during user authentication:
<app-error-message error-message="signinFormController.authError"></app-error-message>
Troubles during unit testing¶
We use Karma to test most of the JavaScript code written for our frontend. But testing AngularJS directives is not as straightforward as testing controllers or services. The official documentation is a good starting point.
describe('App tools directives', function () {
var $compile,
$rootScope,
$templateCache;
beforeEach(module('app'));
beforeEach(module('app.templates'));
beforeEach(inject(function(_$compile_, _$rootScope_, $httpBackend){
$compile = _$compile_;
$rootScope = _$rootScope_;
$httpBackend.whenGET('assets/l10n/en.json').respond('');
}));
it('appErrorMessage should display message', function() {
$rootScope.msg = 'abcd1234';
var element = $compile("<div><span app-error-message error-message=\"msg\"></span></div>")($rootScope);
$rootScope.$digest();
expect(element.html()).toContain("abcd1234");
});
});
Angular translate¶
The first trouble is that we use angular-translate (We currently only support English but everything is ready to translate our GUI in other languages).
If like me you get the error Error: Unexpected request: GET assets/l10n/en.json while running Karma, you may inject $httpBackend before each test and let it expect a GET request to the translation file:
$httpBackend.whenGET('assets/l10n/en.json').respond('');
template URL¶
The next trouble is dealing with template URLs. My directive uses an external template referenced by the line templateUrl:'app/shared/directives/tools/app_error_message.html'.
I got the error message Error: Unexpected request: GET app/shared/directives/tools/app_error_message.html, but would not want to configure my $httpBackend to return the template for each directive. The solution is to install and configure the karma-ng-html2js-preprocessor plugin:
npm install karma-ng-html2js-preprocessor --save-dev
Once installed, there should be a reference to this preprocessor in your package.json file: "karma-ng-html2js-preprocessor": "~0.1.2".
You also need to configure your karma.conf.js file:
module.exports = function (config) {
config.set({
basePath: './',
files: [...],
plugins: [
'karma-chrome-launcher',
...
'karma-ng-html2js-preprocessor'
],
preprocessors: {
'app/**/*.html': ["ng-html2js"]
},
ngHtml2JsPreprocessor: {
// the name of the Angular module to create
moduleName: "app.templates"
}
});
};
And load the module app.templates in your directive test: beforeEach(module('app.templates'));
ng-include¶
Ng-include is a basic directive that allows you to include fetch, compile and include an external HTML fragment. The last trouble I faced was to deal with testing directives that use it. You have to declare an included content in $templateCache to efficiently test it:
$templateCache.put('include.html', '<div>Library</div>');
var element = $compile("<div><span ng-include=\"'include.html'\"></span></div>")($rootScope);
$rootScope.$digest();
expect(element.html()).toContain("<span");
Next step¶
I only had time to test a tiny part of the directives we use in OctoPerf. I would be proud to reach at least 95% test coverage by testing all of them.