Testing Angular2 Apps with Jasmine and TypeScript

Angular2 is currently available in beta2, if you haven't updated yet. Go and check latest bits, because my post is targeting Angular2 beta2.

Update

This sample is also working with angular2 beta3

There are some resources available online when it comes to unit-testing angular2 apps, but for me none of the guides worked from front to end. Also the official doc is not working, so I decided to share what I've learned from writing unit-tests for angular2 apps using TypeScript.

What do you need

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.

Installing the 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.

Add 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.

An actual UnitTest

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,...).

Let's try it

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.

Adding JIT-Transpilation and Reload

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.

Jasmine keeps saying 'No specs found'

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 implementation from the angular2 team are providing an easy integration into core concepts from the framework itself such as DI. Which makes it also easy to test services relying on http oder other framework related stuff.

What are your experiences related to testing angular2 apps using Jasmine? Be so kind and post them as a comment in the section below, or if you've further questions, feel free to ask!

Comments

comments powered by Disqus