Taking Angular 2 for a spin

This article is the successor of the Typescript, Angular 2 and NetBeans IDE: An unbeatable trio article.

Every application, whether native or web-based, should be tested automatically to ensure correctness and prevent regressions. Basically two types of tests are possible: end-to-end tests, testing an application as a whole, or unit tests, which cover a small part of the application in isolation. This article will cover the topic of unit tests.

SEE ALSO: Typescript, Angular 2 and NetBeans IDE: An unbeatable trio

The purpose of Unit Tests serve is to test the various components (“units”) of an application separately and isolated from each other. This isolation is a great benefit as it reduces effort: the whole application doesn’t need to be set up for testing in one big test; only the separate components are tested in small and simple test sets.

Proper unit testing an application (e.g. test-driven development) also results in better reusability and extensibility, because the components and modules themselves have to be isolated from each other in order to be tested separately. When all functions can be tested separately and isolated from other functionalities, reused within the application or even other applications can be assumed since a unit test is already a kind of reuse.

In Angular 2, this separation of concerns is supported by the module system and the introduction of components and services. Instead of writing simple comparisons with language primitives, an assertion library is usually used, which provides a nice and readable API for expressing assertions. Jasmine is an implementation of such a library and will be covered in the next paragraph.

Jasmine

Jasmine is an open source framework for JavaScript tests. It runs on every JavaScript capable device and has no dependency on DOM or other browser APIs. It provides its own assertion library and tests are supposed to be easy to write and easy to comprehend.

A simple test suite in Jasmine may look like this: a suite (“describe”) may contain different test specs (“it”). Each spec can contain different expectations (“expect”) to test the state of the application. An expectation is an assertion that is evaluated to be either true or false using matchers for comparison. If the comparison turns out to be true, the expectation passes the test. Therefore, the following example will trivially pass the test.

describe("A suite", function() { it("contains spec with an expectation", function() { expect(true).toBe(true); }); });

For more information about the Jasmine project, visit their site.

Alternatives to Jasmine are mocha or QUnit, but Jasmine is used officially by the Angular 2 team. This article will therefore only cover Jasmine.

Jasmine tests can be run in a browser, offering direct inspection of the results. In a small project with a limited test set, this might be sufficient. For bigger projects with lots of tests, (build) automation and IDE support are required. This is when the Karma test runner comes into play.

Karma

Karma is a test runner that is able to automatically run test on multiple browsers and devices.

It was originally developed by the AngularJS team with the goal to replace “standalone” JavaScript testing environments which tend to have false positives (errors which were not present in a browser) and vice-versa. Instead of these standalone environments, Karma uses the real runtime environment of a browser (like Firefox or Chrome). The advantage of this approach is that the app is tested in an environment that is similar to the production environment.

Karma cares for the test sets from an internal HTTP server and prepares the test environment by starting the necessary browser. Karma ensures that the browser environment is always available. If the browser crashes during the test, it will be restarted. This is important for automated continuous integration environments. We will use Karma as test runner and Jasmine as assertion library to write the tests.

For more information about the Karma project, visit their homepage.

Typings

Angular 2 applications can be written in TypeScript, a typed superset of the JavaScript ES5 (and also ES2015) standard. Because testing is a major part of development, the tests should be written in TypeScript as well. Jasmine itself is currently only available in ES5 JavaScript. This may not seem overly problematic as TypeScript code can run along with ES5 code. However, using an ES5 library will potentially undermine everything achieved by using TypeScript like IDE support for code completion and analysis, gained by static typing. When simply using Jasmine in tests written in TypeScript, the compiler will complain that it doesn’t know about the types and properties used and only limited IDE support will be available.

To fix this, typing information for the Jasmine library has to be provided to the TypeScript compiler. A simple way to provide those types is via the TypeScript definition manager called Typings. It can be installed via NPM, its usage is analogous to NPM. The Typings which are to be installed will be located in the typings.json file in the project.

In the example, the typings.json looks like:

{ "dependencies": {}, "devDependencies": {}, "ambientDevDependencies": { "jasmine": "github:DefinitelyTyped/DefinitelyTyped/jasmine/jasmine.d.ts#26c98c8a9530c44f8c801ccc3b2057e2101187ee" } }

To include the installation process of Typings in the npm install installation step, the following lines can be added to the package.json:

"scripts": { ... "postinstall": "typings install --ambient", ... } ... "devDependencies": { ... "typings": "^0.7.9", ... }

Now that the preconditions for writing Jasmine tests in TypeScript are met, the remaining test environment can be set up.

Setup of the test environment

Building on the example of the last article, the complete package.json should look like this:

{ "name": "2016-01-angular2-simple", "version": "1.0.1", "author": "Karsten Sitterberg", "scripts": { "postinstall": "typings install --ambient", "pretest": "npm run tsc", "test": "karma start karma.conf.js", "clean": "rm -rf app/*.js* && rm -rf test/*.js*, "distclean": "rm -rf node_modules && rm -rf typings", "tsc": "tsc", "tsc:w": "tsc -w", "lite": "lite-server", "start": "concurrent \"npm run tsc:w\" \"npm run lite\" ", }, "dependencies": { "angular2": "2.0.0-beta.14", "es6-shim": "^0.35.0", "reflect-metadata": "0.1.2", "rxjs": "5.0.0-beta.2", "zone.js": "0.6.6", "systemjs": "0.19.6" }, "devDependencies": { "concurrently": "^1.0.0", "lite-server": "^1.3.1", "jasmine-core": "2.3.2", "karma": "^0.13.22", "karma-chrome-launcher": "^0.2.3", "karma-jasmine": "^0.3.8", "typings": "^0.7.9", "typescript": "^1.8.9" } }

Since the last article, the Angular and TypeScript versions have been updated and the typings, Jasmine and Karma dependencies have been included. In the scripts section, the postinstall script for installing the typings has been added along with the test and pretest scripts to run the unit tests. The clean and distclean scripts have been extended to clean both tests and typings.

Of course the TypeScript compiler is initialized with a tsconfig.json file, which will remain exactly as it was in the last article. The full source is available in the GitHub repositorycodecoster/2016-angular-simple.

The index.html and the application files can remain unchanged, with one exception — the file boot.ts: Since Angular 2.0.0-beta.6 transitive typings are no longer included automatically, so the es6 typings have to be included explicitly. This is is done by using the special “///” comment syntax with a reference as shown at the first line in the file boot.ts:

/// <reference path="../node_modules/typescript/lib/lib.es6.d.ts" /> import {bootstrap} from 'angular2/platform/browser' import {SimpleComponent} from './simple.component' bootstrap(SimpleComponent);

Karma is configured via the karma.conf.js file. Here’s a sample configuration:

module.exports = function (config) { config.set({ basePath: '.', frameworks: ['jasmine'], port: 9876, logLevel: config.LOG_INFO, colors: true, autoWatch: true, browsers: ['Chrome'], // Karma plugins loaded plugins: [ 'karma-jasmine', 'karma-chrome-launcher' ], files: [ // paths loaded by Karma {pattern: 'node_modules/systemjs/dist/system.src.js', included: true, watched: true}, {pattern: 'node_modules/es6-shim/es6-shim.js', included: true, watched: true}, {pattern: 'node_modules/angular2/bundles/angular2-polyfills.js', included: true, watched: true}, {pattern: 'node_modules/rxjs/bundles/Rx.js', included: true, watched: true}, {pattern: 'node_modules/angular2/bundles/angular2.dev.js', included: true, watched: true}, {pattern: 'node_modules/angular2/bundles/testing.dev.js', included: true, watched: true}, {pattern: 'karma-test-shim.js', included: true, watched: true}, // paths loaded via module imports {pattern: 'app/*.js', included: false, watched: true}, {pattern: 'test/*.js', included: false, watched: true}, // paths to support debugging with source maps in dev tools {pattern: 'app/*.js.map', included: false, watched: false}, {pattern: 'test/*.js.map', included: false, watched: false}, {pattern: 'app/*.ts', included: false, watched: false}, {pattern: 'test/*.ts', included: false, watched: false} ] }); };

The configuration properties define the assertion library that is used (Jasmine), the port of the Karma HTTP server, the log level, the browser to run the tests in as well as some other settings that are described in detail on the Karma homepage.

The files property contains all the files required in order to run the application and the tests. In this case, all Angular dependencies will be loaded and executed from Karma. Additionally, a file called karma-test-shim.js is loaded and executed – more details on the shim will be provided at a later time. Then, the (compiled) application and test .js files will be loaded, but not executed. This is performed by the System.js library at a later stage. Finally, the source TypeScript files and the .map files are included to support debugging, for example the Chrome-Dev Tools support this.

As all the app and test files run in the browser, and the browser and Karma itself only understand ES5, just the compiled files will be executed. Additionally, the karma-test-shim.js has to be introduced: This file serves as a shim to provide the System.js module loader of Angular 2 (module loading in Angular 2 is done via System.js) to Karma. In this shim, the original synchronous starting cycle of Karma is cancelled and triggered again at a later stage, when all application and testing files have been loaded by System.js. The karma-test-shim can also be found on GitHub.

The file structure looks like this:

projectRoot/ - index.html - package.json - tsconfig.json - typings.json - karma.conf.js - karma-test-shim.js - app/ - boot.ts - simple.component.ts - test/ - simple.test.ts - node_modules/ - typings/

The file simple.test.ts contains a basic unit test of the component. In TypeScript it looks like this:

import {it, describe, expect, beforeEach} from 'angular2/testing'; import {SimpleComponent} from "../app/simple.component"; describe('Simple Component Test', () => { let component: SimpleComponent; beforeEach(() => { component = new SimpleComponent(); }); it('should be defined', () => { expect(component).toBeDefined(); }); it('should be an implementation of "Component"', () => { expect(component).toBeAnInstanceOf(SimpleComponent); }); });

The first line imports the functions used for testing. Note that the imports point to Angular2/testing instead of Jasmine, because Angular 2 wraps and extends some of Jasmine’s functionality aspects. In the second line, the SimpleComponent is imported, which will be tested.

Test suites are opened with the describe function, which takes a string to describe the suite and a function which contains the code for the suite. Here, a lambda expression is used as function. To directly access the SimpleComponent within the tests, the variable component is declared by let. The beforeEach function makes sure this variable will hold a fresh instance for each test execution. Each test case (or “spec”) is described by the it function, which takes a string as title for the test case and a function which contains the test logic. Jasmine uses function names that support reading the test source: the first example case is read as “It should be defined”.

In this example, two tests are specified. The first one ensures that the component instance is present, which should always be true, as we generate a new component instance before each test. The second spec verifies that the component is an instance of the SimpleComponent class.

Tests on properties of the Component are also possible. In the following example, the name property of the component is tested for its initialization value.

describe('Component initialization Test', () => { let component: SimpleComponent; beforeEach(() => { component = new SimpleComponent(); }); it('should have an initial value of ”here”', () => { expect(component.name).toEqual('here'); }); });

A more elaborate test suite may also include interaction with the compiled runtime-component from within the browser DOM. For example, checking initialization of the DOM, clicking buttons, reaction of the component to events or interacting with (mock)-services can be implemented. A few of these tests will be also included in the GitHub repo.

Integration with NetBeans

To integrate the testing environment, NetBeans needs to know about the Karma test runner. This is done via the NetBeans Options (click “Tools”->”Options”, select the “HTML/JS” pane and the “Karma” tab). By clicking the “Browse…” button, the global installation of the Karma command line interface (Karma-cli) has to be selected (Global, as in “npm install –global karma-cli”).

It is desirable to let Karma look for file changes and rerun the tests if a file has changed. This can be configured in the Project-Settings (right-click the Project and hit “Settings”), in the Sub-Menu “JavaScript Testing”. You can also right-click onto the Karma-Symbol in the Projects-Section of NetBeans (on the bottom of the Project) and choose “Properties”.

In the properties dialog, simply check the “Watch for file changes and rerun tests automatically” checkbox and subsequently click “OK”.

After configuring Karma, you will have different options to run Karma:

You can run the npm-script “test” by right-clicking the project and choosing “npm Scripts -> test”, (this is available even without the former configuration steps). However, in order to leverage the integration of Karma with NetBeans, simply right-click the project and directly hit “Test”.

In both cases, the Chrome browser will be launched to host the test executions, which will look something like this:

If the integrated version of Karma is used, the full Test-Result output from NetBeans will be available, as shown in the following picture.

If the npm-script version of the test is running, only the command line output (like on the right hand side of the image) will be shown.

If all test suites have passed, the test result will be a green bar like in the image. If some tests have failed, the bar will be red and the failed tests will be shown below the bar.

Conclusion

In this article we explored the perks of Angular 2, wrote some basic unit tests for a simple Angular 2 application and showed the integration of the testing environment within the NetBeans IDE.

If you need help with Angular 2 development or front end architecture, check out the articles, consulting and training offered by trion.

You can find a sample project for NetBeans showing Angular 2 testing with Karma and Jasmine on GitHub.