Unit Testing AngularJS Modules

Staff Profiles
Staff Profiles

The front-end team in the Digital Experience department encourages testing the code we write so we can deploy quality applications. We just published our third AngularJS app and plan to use React.js for our future projects. Our latest app, Staff Profiles, highlights NYPL staff experts and it resembles the Research Division app. But, the biggest difference is how we read the data coming from the API. The back-end team is now using the JSONAPI specification for building APIs, which we call the Refinery, and we have enjoyed its modularity and ease of use when reading data.

Our previous approach to calling and reading the NYPL Locations API was custom and specific to the endpoints that were being served through the API. As the JSONAPI site says, the specification is an "anti-bikeshedding weapon" which means we can focus less on the format of the API and focus more on building apps. Since we were building the Staff Profiles app in AngularJS we then created the Parserinator, an AngularJS module to read JSONAPI formatted APIs. This post will focus on setting up any AngularJS module's environment for unit testing, adding mocked data, setting up coverage for the source code, and using ES6 for the source and unit tests. A follow up post will focus on unit testing techniques and patterns.

For brevity, some code was omitted in the Gists in this post, but you can find the full source on Github. The module is stable but still in development so pull requests, feedback, and issues are welcomed!

Set Up

We are using Karma to run our code for testing, Jasmine for the testing framework, and Gulp to automate the testing task. In the module repository, we need to create a package.json file to store the dependencies we need through NPM (Node Package Manager). Now we can install the development dependencies with the following command:

npm install karma jasmine-core gulp --save-dev

We use Gulp to automate multiple tasks and decided to use it to run the Karma runner as well. Setting up a Gulp task is simple and the following setup assumes that the Karma configuration file lives in the /test directory.

Now we can run gulp test in the terminal to see the results of our unit tests.

Karma Configuration

The Gulp task we set up will read the Karma configuration file that will load the files needed for the tests to run. We want to load the AngularJS source file, the angular-mocks plugin which allows us to use the $httpBackend feature in the ngMocks module, the source file, the unit test file, and any other dependencies that the module requires.

We can also load mocked data, frameworks, plugins, reporters, and preprocessors in this configuration file. Check out the complete file on Github.

Mocked Data

With help from the karma-jasmine-jquery plugin, we can load mocked data through JSON files. After setting up the directory where the mocked data lives, we include it in the Karma configuration as a file:

{ pattern: 'test/mock/*.json', watched: true, served: true, included: false }.

In the unit tests, you can obtain the data by calling the `jasmine.getJSONFixtures()` function and assigning fixturesPath to the path where all the mocked files live. Make sure to add 'base' to the front of the directory or else the files won't be read.

This helped us out tremendously because we were able to write more readable unit tests by removing unnecessary mocked data in the file. We can now just import the data in a fashion that resembles requiring NPM packages or ES6 imports.

HTML Preprocessor

The Parserinator module does not contain AngularJS directives but we do have directives that we test in other NYPL AngularJS apps. To test them, we use the karma-ng-html2js-preprocessor plugin which converts a directive's HTML into a module. The module gets loaded in the unit test and the AngularJS app won't have to fetch the directive from the server, which usually cases problems when running unit tests.

Run `npm install karma-ng-html2js-preprocessor --save-dev` to install the plugin and then load it in the Karma configuration:

In the example above, the HTML files live in the /public/scripts/directives/templates directory. We need to load this directory through the 'ng-html2js' preprocessor and assign it a module name. We named the module 'directiveTemplates' and it can be loaded in the unit tests through the module function.

Coverage

The front-end team loosely follows a test-driven development approach; we write tests before we write code but we usually write a lot of code while we build new features and modules. The best way that we have found to keep track of untested lines of code is by using the karma-coverage plugin that tells us how much of our code is tested, or covered. This includes different inputs to our functions, different scenarios and branches for logic workflow, and basic error handling.

After installing the module it needs to be added to the configuration so that it knows what file should be processed.

The results are available when opening up the `../test/coverage/browserName/index.html` file where 'browserName' is the folder name of the browser running the unit test. The result tells us what lines of code still need to be tested, what logic statements are unreachable, what functions have been tested, and more.

Karma Coverage
Coverage Results of the Parserinator AngularJS module

ES6

ECMAScript 6 (ES6) is awesome and we are starting to integrate it into our workflow. The benefits of using ES6 are numerous and we have noticed better, cleaner, and more readable code ever since we've switched. We just touched the surface with ES6 and AngularJS but all our future React.js projects will be written in ES6. We are currently using Babel for the ES6 compiler.

When writing the Parserinator, we decided to use ES6 to show an example of an AngularJS module written in ES6, to figure out a workflow for compiling ES6, and to learn its syntax.

There are two separate tasks related to ES6. The first one is compiling the source from ES6 to ES5 (which browsers can read) to use in AngularJS apps. The second task is compiling the source and the unit tests when Karma runs.

Install Babel through npm:

npm install gulp-babel karma-babel-preprocessor --save-dev

The Gulp task for for compiling the source to distribute looks like this:

To load Babel in Karma, we need to configure the karma.conf.js file. We need to include the NPM module in the plugins array and include what files need to be processed by Babel in the preprocess option.

Now can now enjoy the benefits of writing a module's source and unit tests in ES6. This step is optional but we highly recommend using ES6 when writing Javascript.

We now have an AngularJS module with an environment that is ready for unit testing, along with setting up a coverage tool, a compiler process, and a way to easily include mocked data. A follow up post will focus on writing unit tests in ES6 using Jasmine as the testing framework.

Comments

Patron-generated content represents the views and interpretations of the patron, not necessarily those of The New York Public Library. For more information see NYPL's Website Terms and Conditions.

Review

Thanks for explaining unit testing in angular js modules.