Unit-Testing Angular applications made me crazy during Beta time. In this post, I’ll share how to set up unit testing for Angular 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 beta. Unfortunately, none 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. Things may change over time, so you should check out the latest documentation on https://angular.io.
Prerequirements
You should be able to reproduce the upcoming steps with any of your angular apps. So there is no need for particular structure or something like this.
Install dependencies
To get tests with Jasmine and TypeScript working, let’s install the required dependencies.
npm install jasmine-core concurrently live-server --save-dev --save-exact
The official Angular documentation uses live-server
all over the place. However, I’m using live-server
because it’s faster and smaller than lite-server
, but it’s entirely up to you.
Create a test page
This introduction explains how to execute tests in the browser using a 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
Also, 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>
Examine the code, besides the Angular boilerplate, all Jasmine stuff - stylesheets and scripts - are loaded. Be aware, documentation on angular.io isn’t correct 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 Angular related implementation for testing to work with DI
.
At 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). So that’s what we’re looking at next.
Writing a Unit-Test
In my sample repo, I’ve created 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 an 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 normal module loading right here. Moreover, I’m importing the required things from angular2/testing
to get rid of TypeScript compilation errors (caused by methods like describe
, it
, beforeEach
,…).
Execute Unit-Tests
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 Live-Reload
Manually starting the TypeScript transpilation process each time is time-consuming. Use tsc -w
to get rid of this, it watches our TypeScript files. Concurrently
also starts the web server which directly points 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 starts the compilation process and keeps watching the files. Once tsc
finishes the compilation, the browser displays 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 snippets, and the specs 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 comfortable. The custom implementation from the Angular core team allows you 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 framework itself.