Unittesting Angular 2 applications made me crazy during Beta time. In this post I’ll share how to setup Unittesting for Angular 2 applications. The samples below were tested on Angular 2 Beta 2 and Beta 3. There are many resources available on the web that explain how unit-testing should work in Angular 2 beta. Unfortunately non of them worked for me from front to end. The official documentation is also lacking a fully working example with SystemJS while I’m writing this article. This may change over time so you should definietely check out the latest documentation on https://angular.io.

Prerequirements

You should be able to reproduce the upcoming steps with any of your angular2 apps. So there is no need for special structure or something like this.

Install dependencies

In order to get tests with Jasmine and TypeScript working, let’s install the required dependencies.

$ npm i jasmine-core concurrently live-server --save-dev --save-exact

I’m using live-server because it’s faster and smaller than lite-server that is somewhere listed on angular.io, but it’s totally up to you.

Create a test page

For this introduction I will only cover how to execute tests in the browser using a dead simple tests.html file. Executing the tests in a headless mode is a topic for another post. That said, let’s go and create the tests.html file

$ touch tests.html

And provide the following content

<html>
<head>
    <title>testing ng2 apps with jasmine</title>
    <link rel="stylesheet" href="/node_modules/jasmine-core/lib/jasmine-core/jasmine.css">
    <!-- Jasmine Scripts -->
    <script src="/node_modules/jasmine-core/lib/jasmine-core/jasmine.js"></script>
    <script src="/node_modules/jasmine-core/lib/jasmine-core/jasmine-html.js"></script>
    <!-- Jasine's main.js has recently be renamed to boot.js -->
    <script src="/node_modules/jasmine-core/lib/jasmine-core/boot.js"></script>
    <!-- Common dependencies for ng2 -->
    <script src="/node_modules/es6-shim/es6-shim.min.js"></script>
    <script src="/node_modules/systemjs/dist/system-polyfills.js"></script>
    <script src="/node_modules/angular2/bundles/angular2-polyfills.js"></script>
    <script src="/node_modules/systemjs/dist/system.src.js"></script>
    <script src="/node_modules/rxjs/bundles/Rx.js"></script>
    <script src="/node_modules/angular2/bundles/angular2.dev.js"></script>
    <script src="/node_modules/angular2/bundles/router.js"></script>
    <!-- Import ng2 testing -->
    <script src="/node_modules/angular2/bundles/testing.dev.js"></script>
</head>

<body>
    <script>
            System.config({
            packages: {
                'app': {
                    defaultExtension: 'js'
                }
            }
        });
        System.import('app/components/app/app.spec')
            .then(window.onload)
            .catch(console.error.bind(console));
    </script>
</body>
</html>

There are some interesting things going on here. Besides the angular2 boilerplate, we’re loading all jasmine stuff (css and scripts). Be aware, documentation on angular.io is broken right here. jasmine-core has no main.js as described in their docs. You’ve to load boot.js.

Another interesting part is the script node for testing.dev.js, you’ve to reference this file if you want tsc to work as expected and to get the angular2 related implementation for testing to work with DI.

In the end of the HTML file, I’m importing the app.spec. That’s pointing to our transpiled app.spec.ts file (see the defaultExtension property for the app package). And that’s what we’re looking at next.

Writing a Unit-Test

In my sample repo, I’ve create a dead-simple AppComponent which is exposing a property called items to the view. So we could take this under test to see everything is working as expected. The AppComponent is defined in a app.ts file which is located in app/components/app/app.ts and that’s exactly where I will create the spec-file.

$ touch app/components/app/app.spec.ts

Let’s write a simple test

import {AppComponent} from './app';
import {describe, it, beforeEach, expect} from 'angular2/testing';

describe('AppComponent', () => {
    var app: AppComponent = null;

    beforeEach(()=>{
        app = new AppComponent();
    });

    it('should have an items property', () => {

        expect(app.items).toBeDefined();
    });
});

As you can see, I’m using regular module loading right here. and I’m importing the required things from angular2/testing in order to get rid of TypeScript compilation errors (caused by methods like describe, it, beforeEach,…).

Execute Unit-Tests

In order to get the results from our simple test, you’ve to execute a few commands.

$ ./node_modules/.bin/tsc
$ ./node_modules/.bin/live-server --open=tests.html

Your browser should fire-up and display the following result.

Add JIT-Transpilation and Livereload

Starting TypeScript transpilation process each and every time manually is time-consuming. To get rid of this, we will now use tsc -w which will watch on our TypeScript files. Using concurrently we will also start the webserver which will directly point to the tests.html site. To do so, ensure that the following scripts are located in your package.json

"scripts":{
  "tsc": "tsc",
  "tsc:w": "tsc -w",
  "live": "live-server",
  "test": "live-server --open=tests.html",
  "test:w": "concurrent \"npm run tsc:w\" \"npm run test\" "
}

Save the file, go to your terminal and execute

$ npm run test:w

Now typescript will start compilation process and keep watching your files. As soon as tsc is done your browser will display the expected result.

Fix common issues thrown by Jasmine

If your setup is fine and Jasmine keeps on reporting ‘No specs found’, you made just a small mistake. The root cause is that Jasmine has finished looking for specs when systemJS is ready for loading the spec files. To fix this, ensure that window.onload is called right after systemJS has loaded your spec-files. Change the part of your testing page to match the following and specs will appear.

System.import('app/components/app/app.spec')
            .then(window.onload)
            .catch(console.error.bind(console));

Recap

As you can see, using Jasmine in TypeScript is easy and the custom implementations from the Angular 2 core team are providing an easy integration into core concepts from the framework itself such as dependency injection. That said, it’s also easy to test services relying on http oder other services exposed by the Angular 2 framework itself.