Since Angular’s official release in September 2016, a lot of ink has been spilled over the ground-up rewrite of AngularJS.
While the quantity of informations is huge, it seldom addresses tips on how to solve specific problems. This has led me to write this serie of posts about my journey with Angular.

In this post, I will focus on testing tips. If you are unfamiliar with Angular testing, you can check out this very good tutorial from codecraft.

Destimator

First, I created an application called Destimator which estimates your death risk. Pretty crazy right ! Well it was hard to find an original idea with all those demo apps in the wild but no worries, nothing is scary, after all we are here to talk Angular ;)
It is splitted into two parts:

  • estimation-form: Responsible for rendering the form and display the result.
    • estimation-form.component.ts: Rendering component
    • estimation-form-component.html: Component template
    • estimation-info.model.ts: form information model
    • estimation-result.model.ts: form result model
  • estimation-logic: Business logic for calculating death risk.
    • estimation-logic.service.ts: service to calculate then estimate death risk
    • score-range.service.ts: service to retrive data from api
    • score-range.model.ts: model for communicating with api

Destimator’s repo is publicly available on my github here. If you dare to try it
Now, that we have a basic understanding of the roles, we can move to the testing part.

Testing

Frameworks tip

The project was generated using Angular-CLI. The testing uses the very popular Jasmine framework for testing, asserting and even mocking. I prefer to let Jasmine take care of the testing part only while Chai handle the assertion part and Sinon the mocking part.
It’s a choice driven by Separation of Concerns (one thing should do one thing only and do it well) although some might argue that this is JavaScript fatigue all over again.

Bare with me for the time being, I hope to convince you by this post’s end.

1
npm install -D chai sinon

First test technique

Testing an angular component consists on preparing a standalone module that will contain every dependency your component and its template require to run. While most people will copy paste or generate a testing file, I prefer to start with an empty module. Why ? Because understanding dependencies helps me while debugging and tuning my application.
In this example, I want to test my estimation-form.component, so my approach is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { EstimationFormComponent } from 'app/estimation-form/estimation-form.component';
import { expect } from 'chai';
describe('estimation form', () => {
let fixture: ComponentFixture<EstimationFormComponent>;
let comp: EstimationFormComponent;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [],
declarations: [
EstimationFormComponent
],
providers: []
});
fixture = TestBed.createComponent(EstimationFormComponent);
comp = fixture.componentInstance;
});
it('should have cheatingOnPartnerLvl, adrenalineDose, hasSuperHeroFriend as form keys when component initialize', () => {
comp.ngOnInit();
expect(Object.keys(comp.estimationForm.value)).to.be.eql(['cheatingOnPartnerLvl', 'adrenalineDose', 'hasSuperHeroFriend', 'email']);
});
});

As you can see, my test is simply checking if I have set the right keys in my reactive form group. Although the test will very likely pass (unless I made a typo somewhere), it won’t run because my testing module doesn’t contain my component’s dependencies.
Let’s try to run it and see the error:
1
npm test

The output is:
1
2
Error: Template parse errors:
Can't bind to 'formGroup' since it isn't a known property of 'div'. ("<div class="row" [ERROR ->][formGroup]="estimationForm">

Well, this means that our component is dependent on the ReactiveFormsModule which contains the formGroup directive. With some little magic:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { EstimationFormComponent } from 'app/estimation-form/estimation-form.component';
import { expect } from 'chai';
import { ReactiveFormsModule } from '@angular/forms';
describe('estimation form', () => {
let fixture: ComponentFixture<EstimationFormComponent>;
let comp: EstimationFormComponent;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
// Adding the required module
ReactiveFormsModule
],
declarations: [
EstimationFormComponent
],
providers: []
});
...

If we run it again, a new error appears:
1
2
3
Error: Template parse errors:
'md-icon' is not a known element:
If 'md-icon' is an Angular component, then verify that it is part of this module.

Again, our component’s template is using Angular Material components without importing the MaterialModule in our testing module. Fix it by importing that module.
I won’t go through all error messages because it’s not my point. What I wanted to explain by the simple test approach is that you probably need to spend some time understanding what your component requires to run before starting to test it. This might seems time consuming but if one single test can help remove duplicated dependencies, optimize my code and discover more about Angular modules, it might be a good time investment.

Isolation technique

Let’s fast forward to where we finish setting up all dependencies, our file should look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { EstimationFormComponent } from 'app/estimation-form/estimation-form.component';
import { ReactiveFormsModule } from '@angular/forms';
import { expect } from 'chai';
import { MaterialModule } from '@angular/material';
import { EstimationLogicService } from 'app/estimation-logic/estimation-logic.service';
import { ScoreRangeService } from 'app/estimation-logic/score-range.service';
import { HttpModule } from '@angular/http';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
describe('estimation form', () => {
let fixture: ComponentFixture<EstimationFormComponent>;
let comp: EstimationFormComponent;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
ReactiveFormsModule,
MaterialModule,
HttpModule,
BrowserAnimationsModule
],
declarations: [
EstimationFormComponent
],
providers: [
EstimationLogicService,
ScoreRangeService
]
});
fixture = TestBed.createComponent(EstimationFormComponent);
comp = fixture.componentInstance;
});
it('should have cheatingOnPartnerLvl, adrenalineDose, hasSuperHeroFriend as form keys when component initialize', () => {
comp.ngOnInit();
expect(Object.keys(comp.estimationForm.value)).to.be.eql(['cheatingOnPartnerLvl', 'adrenalineDose', 'hasSuperHeroFriend']);
});
});

The test passes successfully but did you notice something weird about our dependencies ?
We are importing the HttpModule. How is this possible?! Tests are supposed to be isolated.
Well, if we take a look at the ScoreRangeService:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Injectable()
export class ScoreRangeService {
// Http dependency
constructor(private http: Http) { }
getRange(score: number): Observable<ScoreRange> {
return this.getRanges()
.map(res => res.filter(r => score >= r.maxScore))
.map(res => res[res.length - 1]);
}
private getRanges(): Observable<ScoreRange[]> {
return this.http.get('../../api/score.range.json')
.map(res => res.json().ranges as ScoreRange[]);
}
}

We will notice the dependency on the http service in the constructor, so how can we eliminate that. Certainly, we won’t change our service’s implementation but rather modify how our testing module is providing it by using a fake implementation.
How can we create a fake implementation ? This is where Sinon shines ! Told you we will get back to this ;)
By using the createStubInstance method of Sinon with no effort. The mock consists of an object with the same keys and signatures as the original implementation so no Angular nor typescript will detect the difference.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { EstimationFormComponent } from 'app/estimation-form/estimation-form.component';
import { ReactiveFormsModule } from '@angular/forms';
import { expect } from 'chai';
import { MaterialModule } from '@angular/material';
import { EstimationLogicService } from 'app/estimation-logic/estimation-logic.service';
import { ScoreRangeService } from 'app/estimation-logic/score-range.service';
import { createStubInstance } from 'sinon';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
describe('estimation form', () => {
let fixture: ComponentFixture<EstimationFormComponent>;
let comp: EstimationFormComponent;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
// Notice that we got rid if the HttpModule !
ReactiveFormsModule,
MaterialModule,
BrowserAnimationsModule
],
declarations: [
EstimationFormComponent
],
providers: [
EstimationLogicService,
{ provide: ScoreRangeService, useValue: createStubInstance(ScoreRangeService) }
]
});
fixture = TestBed.createComponent(EstimationFormComponent);
comp = fixture.componentInstance;
});
it('should have cheatingOnPartnerLvl, adrenalineDose, hasSuperHeroFriend as form keys when component initialize', () => {
comp.ngOnInit();
expect(Object.keys(comp.estimationForm.value)).to.be.eql(['cheatingOnPartnerLvl', 'adrenalineDose', 'hasSuperHeroFriend']);
});
});

While this is a quick way to get tests up and running, it’s not the most powerful one though. Check out the next tip to understand why.

Isolation + Spy technique

We pick up from where we left, excited with our latest discovery createStubInstanc from Sinon. Let’s do the same for our EstimationLogicService:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
...
describe('estimation form', () => {
let fixture: ComponentFixture<EstimationFormComponent>;
let comp: EstimationFormComponent;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
ReactiveFormsModule,
MaterialModule,
BrowserAnimationsModule
],
declarations: [
EstimationFormComponent
],
providers: [
{ provide: EstimationLogicService, useValue: createStubInstance(EstimationLogicService) },
{ provide: ScoreRangeService, useValue: createStubInstance(ScoreRangeService) }
]
});
fixture = TestBed.createComponent(EstimationFormComponent);
comp = fixture.componentInstance;
});
...
});

Imagine now, we are writing a second test to verify that our EstimationLogicService is called when a valid form is submitted:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
...
it('should call estimationLogicService when form is valid and submit executed', () => {
comp.ngOnInit();
// creating a spy to listen for methods/properties invocations
const estimationLogicSpy = spy(estimationLogicServiceStub, 'estimate');
comp.estimationForm.setValue({
'cheatingOnPartnerLvl': 1,
'adrenalineDose': 50,
'hasSuperHeroFriend': false,
'email': 'tuto@ng.io'
});
comp.submitForm();
expect(estimationLogicSpy.called).to.be.true;
});
});

Technically, this would work but in practice you will encounter this error:
1
2
TypeError: Attempted to wrap undefined property estimate as function
at wrapMethod (http://localhost:9876/base/src/test.ts?ae8b7e95826ca6c1ffc4a2d5aadc1d417ff137d0:27854:21)

The message explain that we can’t wrap an already wrapped function. Remember we created a stub using createStubInstance then we used a spy which in turn will attempt to create a stub from a stub. Unfortunately, Sinon doesn’t support chaining stubs so what is the solution ?
Javascript magic ! This might sound stupid but actually it works fine and it’s also very useful.
My advice in such situation is to create fake shell classes, same method signatures but without a body, just like an empty shell. Check out the implementation for FakeEstimationLogicService:
1
2
3
4
5
6
7
8
9
10
11
import { Injectable } from '@angular/core';
import { EstimationInfo } from 'app/estimation-form/estimation-info.model';
import { EstimationResult } from 'app/estimation-form/estimation-result.model';
import { Observable } from 'rxjs/Observable';
@Injectable()
export class FakeEstimationLogicService {
constructor() { }
estimate(formValues: EstimationInfo): Observable<EstimationResult> {
return Observable.of();
}
}

Then, we go back to our testing module and we make some changes:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
...
import { FakeEstimationLogicService } from 'fake/estimation-logic.service.fake';
describe('estimation form', () => {
let fixture: ComponentFixture<EstimationFormComponent>;
let comp: EstimationFormComponent;
let estimationLogicServiceStub: EstimationLogicService;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
ReactiveFormsModule,
MaterialModule,
BrowserAnimationsModule
],
declarations: [
EstimationFormComponent
],
providers: [
{ provide: EstimationLogicService, useClass: FakeEstimationLogicService },
{ provide: ScoreRangeService, useValue: createStubInstance(ScoreRangeService) }
]
});
fixture = TestBed.createComponent(EstimationFormComponent);
comp = fixture.componentInstance;
estimationLogicServiceStub = TestBed.get(EstimationLogicService);
});

and Voilà ! it works like a charm ;)

AsyncTests

My last tip is about async tests. It’s a special kind of tests designed for asynchrounous code. What’s special about asynchrounous code? We don’t know exactly when the result will be avaiblable. It’s just a promise that we should examine at the right time otherwise our assertion won’t make any sense.
Let’s take the example of our email input. Instead of immediatly checking the input when the user starts typing, we want to give him/her some time to finish up. That should provide a better user experience.

In this case, we will defer checking format by 500 ms using debouceTime operator like the code below:

1
2
3
this.estimationForm.controls['email'].valueChanges
.debounceTime(500)
.subscribe(res => this.isValidEmail = this.estimationForm.controls['email'].valid);

Let’s write a test for this:

1
2
3
4
5
6
7
8
9
10
11
12
13
it('should display error message when email is not valid', () => {
comp.ngOnInit();
comp.estimationForm.setValue({
cheatingOnPartnerLvl: 2,
adrenalineDose: 29,
hasSuperHeroFriend: false,
email: 'bad_email_format'
});
expect(comp.isValidEmail).to.be.false;
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('.alert.alert-danger'))
.nativeElement.innerText).to.be.eql('Please enter a valid email address');
});

When this test executes, our expect assertion will simply happen before debounceTime has finished so it will fail. How can we fix it ? Well, some special tools are required.

fakeAsync and tick

fakeAsync and tick are provided by @angular/core/testing. The fakeAsync executes our test in a fake async zone while tick blocks execution and simulates time passage.
Let’s see how our test will look like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
it('should display error message when email is not valid', fakeAsync(() => {
comp.ngOnInit();
comp.estimationForm.setValue({
cheatingOnPartnerLvl: 2,
adrenalineDose: 29,
hasSuperHeroFriend: false,
email: 'bad_email_format'
});
tick(500);
expect(comp.isValidEmail).to.be.false;
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('.alert.alert-danger'))
.nativeElement.innerText).to.be.eql('Please enter a valid email address');
}));

async and whenStable

Although the syntax is a little different but the purpose stays he same. The async wraps the code in an async zone while whenStable will only execute when all pending promises are resolved. This is a rewrite of the previous test using async and whenStable:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
it('should display error message when email is not valid using async', async(() => {
fixture.whenStable().then(() => {
expect(comp.isValidEmail).to.be.false;
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('.alert.alert-danger'))
.nativeElement.innerText).to.be.eql('Please enter a valid email address');
});
comp.ngOnInit();
comp.estimationForm.setValue({
cheatingOnPartnerLvl: 2,
adrenalineDose: 29,
hasSuperHeroFriend: false,
email: 'bad_email_format'
});
}));

jasmine done

While the previous tools were provided by Angular, Jasmine also provides its own mecanism to test asynchronous code. It’s based on a function usually called done and passed into the parameters of the test. This function should be called when all async processing is complete like the following example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
it('should display error message when email is not valid using done', (done) => {
comp.ngOnInit();
comp.estimationForm.setValue({
cheatingOnPartnerLvl: 2,
adrenalineDose: 29,
hasSuperHeroFriend: false,
email: 'bad_email_format'
});
fixture.whenStable().then(() => {
expect(comp.isValidEmail).to.be.false;
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('.alert.alert-danger'))
.nativeElement.innerText).to.be.eql('Please enter a valid email address');
});
done();
});

Like demonstrated above, there are 3 different approaches to test async code, it’s up to the developer to choose. Personally, my favorite is the first one with fakeAsync and tick because it make my code look synchronous therefore easier to understand and debug.

This is the end of this first post on Angular journey, stay tuned for what’s next !