TestBed
TestBed jest najważniejszym narzędziem do testowania aplikacji pisanych w Angular 2/4. Używając go tworzymy klasę typu @NgModule, którą później konfigurujemy za pomocą metody configureTestingModule otrzymując pełnoprawny moduł Angular'owy możliwy do testowania. Sama metoda configureTestingModule przyjmuje jako argumenty te same informacje jakich używamy w trakcie tworzenia modułu aplikacji - importy, deklaracje, schematy etc.
Na pierwszy ogień do testowania weźmy bardzo prosty komponent - HeaderComponent. Naszym celem w tej części będzie przetestowanie inicjalizacji modułu oraz czy tytuł zgadza się z oczekiwanym.
Testowany moduł ma następującą strukturę:
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.css']
})
export class HeaderComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}
Jego szablon HTML wygląda następująco:
<md-toolbar color="primary">
<span>Beers App</span>
<a md-button [routerLink]="'/'">Start</a>
<a md-button [routerLink]="'/random'">Random</a>
<a md-button [routerLink]="'/favourite'">Favourite</a>
</md-toolbar>
Aby napisać zestaw testów do tego komponentu tworzymy w jego katalogu plik header.component.spec.ts i umieszczamy następującą treść:
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { DebugElement } from '@angular/core';
import { HeaderComponent } from './header.component';
describe('HeaderComponent', () => {
let component: HeaderComponent;
let fixture: ComponentFixture<HeaderComponent>;
let debugElement: DebugElement;
let element: HTMLElement;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ HeaderComponent ]
}).compileComponents();
}));
beforeEach(async(() => {
fixture = TestBed.createComponent(HeaderComponent);
component = fixture.componentInstance;
debugElement = fixture.debugElement.query(By.css('span'));
element = debugElement.nativeElement;
fixture.detectChanges();
}));
it('should be created', () => {
expect(component).toBeTruthy();
});
});
Przed uruchomieniem samego testu omówmy podstawowe rzeczy, które są w nim wykorzystane.
createComponent
Klasa TestBed posiada podstawową metodę - createComponent. Jej zadaniem jest utworzenie tzw. component test fixure. Jest to wskaźnik na środowisko testowe utworzonego komponentu. Daje ono dostęp zarówno do instancji samego komponentu jak i obiektu typu DebugElement.
debugElement
Property debugElement klasy ComponentFixture jest wskaźnikiem do elementu DOM testowanego komponentu. Dzięki metodzie query możemy za pomocą selektorów (na przykład) CSS otrzymać element HTML komponentu.
detectChanges
Fundamentalną częscią biblioteki Angular jest detekcja zmian w komponentach. Aby w teście powiedzieć bibliotece, że takowa zmiana nastąpiła wywołujemy metodę detectChanges co dokonuje wszelkich niezbędnych zmian w komponencie na wskutek zmian w nim.
Uruchomienie testu
Po uruchomieniu naszego testu otrzymamy komunikat:
Can't bind to 'routerLink' since it isn't a known property of 'a'.
Stało się tak ponieważ w naszym module testowym nie zaimportowaliśmy modułu odpowiedzialnego za nawigację, który jest wykorzystany w szablonie komponentu (atrybut [routerLink]). Aby w środowisku testowym taki moduł zaimportować należy użyć klasy RouterTestingModule.
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { RouterTestingModule } from '@angular/router/testing';
import { DebugElement } from '@angular/core';
import { HeaderComponent } from './header.component';
describe('HeaderComponent', () => {
let component: HeaderComponent;
let fixture: ComponentFixture<HeaderComponent>;
let debugElement: DebugElement;
let element: HTMLElement;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ HeaderComponent ],
imports: [RouterTestingModule]
}).compileComponents();
}));
beforeEach(async(() => {
fixture = TestBed.createComponent(HeaderComponent);
component = fixture.componentInstance;
debugElement = fixture.debugElement.query(By.css('span'));
element = debugElement.nativeElement;
fixture.detectChanges();
}));
it('should be created', () => {
expect(component).toBeTruthy();
});
});
Po ponownym uruchomieniu testu otrzymamy inny komunikat o błędzie:
'md-toolbar' is not a known element
Stało się tak ponieważ procesor szablonów w Angularze nie wie czym jest atrybut md-toolbar. W naszym przypatku jest to właściwość CSS. Aby poinformować o tym Angulara w teście musimy dodatkowo ustawić schemat na CUSTOM_ELEMENTS_SCHEMA. Prawidłowy i pomyślnie przechodzący test wygląda następująco:
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { CUSTOM_ELEMENTS_SCHEMA, DebugElement } from '@angular/core';
import { RouterTestingModule } from '@angular/router/testing';
import { HeaderComponent } from './header.component';
describe('HeaderComponent', () => {
let component: HeaderComponent;
let fixture: ComponentFixture<HeaderComponent>;
let debugElement: DebugElement;
let element: HTMLElement;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ HeaderComponent ],
imports: [RouterTestingModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
}).compileComponents();
}));
beforeEach(async(() => {
fixture = TestBed.createComponent(HeaderComponent);
component = fixture.componentInstance;
debugElement = fixture.debugElement.query(By.css('span'));
element = debugElement.nativeElement;
fixture.detectChanges();
}));
it('should be created', () => {
expect(component).toBeTruthy();
});
});
Testowanie właściwości elementu
Aby przetestować czy nasz komponent nagłówka zawiera poprawny tytuł musimy dokonać sprawdzenia zawartości elementu span. Robimy to w następujący sposób:
it('should have title', () => {
expect(element.textContent).toContain('Beers App');
});
Automatyczna detekcja zmian
Aby w naszych testach nie musieć za każdym razem wywoływać detekcji zmian możemy użyć specjalnego provider'a - ComponentFixtureAutoDetect. Jego konfiguracja wygląda następująco:
import { ComponentFixtureAutoDetect } from '@angular/core/testing';
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ HeaderComponent ],
imports: [RouterTestingModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
providers: [
{ provide: ComponentFixtureAutoDetect, useValue: true }
]
}).compileComponents();
}));