Angular 2
Testen einer Angular 2 App
Suche…
Installation des Jasmine-Testframeworks
Die gebräuchlichste Methode zum Testen von Angular 2-Apps ist das Jasmine-Test-Framework. Mit Jasmine können Sie Ihren Code im Browser testen.
Installieren
Alles, was Sie brauchen, ist das jasmine-core
Paket (nicht jasmine
).
npm install jasmine-core --save-dev --save-exact
Überprüfen
Um zu überprüfen, ob Jasmine ordnungsgemäß eingerichtet ist, erstellen Sie die Datei ./src/unit-tests.html
mit folgendem Inhalt und öffnen Sie sie im Browser.
<!DOCTYPE html> <html> <head> <meta http-equiv="content-type" content="text/html;charset=utf-8"> <title>Ng App Unit Tests</title> <link rel="stylesheet" href="../node_modules/jasmine-core/lib/jasmine-core/jasmine.css"> <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> <script src="../node_modules/jasmine-core/lib/jasmine-core/boot.js"></script> </head> <body> <!-- Unit Testing Chapter #1: Proof of life. --> <script> it('true is true', function () { expect(true).toEqual(true); }); </script> </body> </html>
Einrichten von Tests mit Gulp, Webpack, Karma und Jasmine
Als Erstes müssen wir Karma anweisen, unsere Tests mithilfe von Webpack zu lesen. Dies ist eine Konfiguration, die wir für die Webpack-Engine festgelegt haben. Hier verwende ich Babel, weil ich meinen Code in ES6 schreibe. Sie können dies für andere Flavours, wie beispielsweise TypScript, ändern. Oder ich verwende Pug-Vorlagen (früher Jade-Vorlagen).
Trotzdem bleibt die Strategie gleich.
Dies ist also eine Webpack-Konfiguration:
const webpack = require("webpack");
let packConfig = {
entry: {},
output: {},
plugins:[
new webpack.DefinePlugin({
ENVIRONMENT: JSON.stringify('test')
})
],
module: {
loaders: [
{
test: /\.js$/,
exclude:/(node_modules|bower_components)/,
loader: "babel",
query:{
presets:["es2015", "angular2"]
}
},
{
test: /\.woff2?$|\.ttf$|\.eot$|\.svg$/,
loader: "file"
},
{
test: /\.scss$/,
loaders: ["style", "css", "sass"]
},
{
test: /\.pug$/,
loader: 'pug-html-loader'
},
]
},
devtool : 'inline-cheap-source-map'
};
module.exports = packConfig;
Und dann brauchen wir eine karma.config.js-Datei, um diese Webpack-Konfiguration zu verwenden:
const packConfig = require("./webpack.config.js");
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine'],
exclude:[],
files: [
{pattern: './karma.shim.js', watched: false}
],
preprocessors: {
"./karma.shim.js":["webpack"]
},
webpack: packConfig,
webpackServer: {noInfo: true},
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
browsers: ['PhantomJS'],
concurrency: Infinity,
autoWatch: false,
singleRun: true
});
};
Bis jetzt haben wir Karma angewiesen, Webpack zu verwenden, und wir haben gesagt, dass es mit einer Datei namens karma.shim.js beginnen soll . Diese Datei hat die Aufgabe, als Ausgangspunkt für das Webpack zu fungieren. webpack wird diese Datei lesen und den Import verwenden und benötigen Aussagen alle unsere Abhängigkeiten zu sammeln und unsere Tests durchführen.
Schauen wir uns nun die Datei karma.shim.js an:
// Start of ES6 Specific stuff
import "es6-shim";
import "es6-promise";
import "reflect-metadata";
// End of ES6 Specific stuff
import "zone.js/dist/zone";
import "zone.js/dist/long-stack-trace-zone";
import "zone.js/dist/jasmine-patch";
import "zone.js/dist/async-test";
import "zone.js/dist/fake-async-test";
import "zone.js/dist/sync-test";
import "zone.js/dist/proxy-zone";
import 'rxjs/add/operator/map';
import 'rxjs/add/observable/of';
Error.stackTraceLimit = Infinity;
import {TestBed} from "@angular/core/testing";
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting} from "@angular/platform-browser-dynamic/testing";
TestBed.initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting());
let testContext = require.context('../src/app', true, /\.spec\.js/);
testContext.keys().forEach(testContext);
Im Wesentlichen importieren wir TestBed aus Winkelkerntests und initiieren die Umgebung, da sie für alle unsere Tests nur einmal initiiert werden muss. Dann gehen wir rekursiv durch das Verzeichnis src / app und lesen jede Datei, die mit .spec.js endet, und geben sie an testContext weiter, damit sie ausgeführt werden.
Normalerweise versuche ich, meine Prüfungen am selben Ort wie die Klasse abzulegen. Der persönliche Geschmack macht es mir einfacher, Abhängigkeiten und Refactor-Tests mit Klassen zu importieren. Wenn Sie Ihre Tests jedoch an einem anderen Ort ablegen möchten, beispielsweise im Verzeichnis src / test , haben Sie die Chance. Ändern Sie die Zeile vor dem letzten in der Datei karma.shim.js.
Perfekt. was ist übrig? ah, die gulp-Aufgabe, die die oben erstellte Datei karma.config.js verwendet:
gulp.task("karmaTests",function(done){
var Server = require("karma").Server;
new Server({
configFile : "./karma.config.js",
singleRun: true,
autoWatch: false
}, function(result){
return result ? done(new Error(`Karma failed with error code ${result}`)):done();
}).start();
});
Ich starte den Server jetzt mit der von uns erstellten Konfigurationsdatei und sage, dass er einmal ausgeführt werden soll und nicht auf Änderungen achten muss. Ich finde, dass dies für mich besser geeignet ist, da die Tests nur ausgeführt werden, wenn ich bereit bin, sie auszuführen. Wenn Sie jedoch einen anderen Computer benötigen, wissen Sie, wo Sie Änderungen vornehmen müssen.
Und als letztes Codebeispiel finden Sie hier eine Reihe von Tests für das Angular 2-Tutorial "Tour of Heroes".
import {
TestBed,
ComponentFixture,
async
} from "@angular/core/testing";
import {AppComponent} from "./app.component";
import {AppModule} from "./app.module";
import Hero from "./hero/hero";
describe("App Component", function () {
beforeEach(()=> {
TestBed.configureTestingModule({
imports: [AppModule]
});
this.fixture = TestBed.createComponent(AppComponent);
this.fixture.detectChanges();
});
it("Should have a title", async(()=> {
this.fixture.whenStable().then(()=> {
expect(this.fixture.componentInstance.title).toEqual("Tour of Heros");
});
}));
it("Should have a hero", async(()=> {
this.fixture.whenStable().then(()=> {
expect(this.fixture.componentInstance.selectedHero).toBeNull();
});
}));
it("Should have an array of heros", async(()=>
this.fixture.whenStable().then(()=> {
const cmp = this.fixture.componentInstance;
expect(cmp.heroes).toBeDefined("component should have a list of heroes");
expect(cmp.heroes.length).toEqual(10, "heroes list should have 10 members");
cmp.heroes.map((h, i)=> {
expect(h instanceof Hero).toBeTruthy(`member ${i} is not a Hero instance. ${h}`)
});
})));
it("Should have one list item per hero", async(()=>
this.fixture.whenStable().then(()=> {
const ul = this.fixture.nativeElement.querySelector("ul.heroes");
const li = Array.prototype.slice.call(
this.fixture.nativeElement.querySelectorAll("ul.heroes>li"));
const cmp = this.fixture.componentInstance;
expect(ul).toBeTruthy("There should be an unnumbered list for heroes");
expect(li.length).toEqual(cmp.heroes.length, "there should be one li for each hero");
li.forEach((li, i)=> {
expect(li.querySelector("span.badge"))
.toBeTruthy(`hero ${i} has to have a span for id`);
expect(li.querySelector("span.badge").textContent.trim())
.toEqual(cmp.heroes[i].id.toString(), `hero ${i} had wrong id displayed`);
expect(li.textContent)
.toMatch(cmp.heroes[i].name, `hero ${i} has wrong name displayed`);
});
})));
it("should have correct styling of hero items", async(()=>
this.fixture.whenStable().then(()=> {
const hero = this.fixture.nativeElement.querySelector("ul.heroes>li");
const win = hero.ownerDocument.defaultView ||hero.ownerDocument.parentWindow;
const styles = win.getComputedStyle(hero);
expect(styles["cursor"]).toEqual("pointer", "cursor should be pointer on hero");
expect(styles["borderRadius"]).toEqual("4px", "borderRadius should be 4px");
})));
it("should have a click handler for hero items",async(()=>
this.fixture.whenStable().then(()=>{
const cmp = this.fixture.componentInstance;
expect(cmp.onSelect)
.toBeDefined("should have a click handler for heros");
expect(this.fixture.nativeElement.querySelector("input.heroName"))
.toBeNull("should not show the hero details when no hero has been selected");
expect(this.fixture.nativeElement.querySelector("ul.heroes li.selected"))
.toBeNull("Should not have any selected heroes at start");
spyOn(cmp,"onSelect").and.callThrough();
this.fixture.nativeElement.querySelectorAll("ul.heroes li")[5].click();
expect(cmp.onSelect)
.toHaveBeenCalledWith(cmp.heroes[5]);
expect(cmp.selectedHero)
.toEqual(cmp.heroes[5], "click on hero should change hero");
})
));
});
Bemerkenswert dabei ist, wie wir vorEach () ein Testmodul konfigurieren und die Komponente in test erstellen, und wie wir detectChanges () nennen, so dass Angular tatsächlich die Doppelbindung durchläuft.
Beachten Sie, dass jeder Test ein Aufruf von async () ist und immer wartet, bis das timeStable- Versprechen aufgelöst wird, bevor das Gerät untersucht wird. Es hat dann Zugriff auf die Komponente über ComponentInstance und auf das Element über nativeElement .
Es gibt einen Test, bei dem das korrekte Styling geprüft wird. Das Angular-Team demonstriert die Verwendung von Stilen in Komponenten. In unserem Test verwenden wir getComputedStyle () , um zu prüfen, ob die Stile von dem von uns angegebenen stammen. Wir benötigen jedoch das Window-Objekt, und wir erhalten es vom Element, wie Sie im Test sehen können.
HTTP-Dienst testen
Normalerweise rufen Dienste Remote-API an, um Daten abzurufen / zu senden. Bei Unit-Tests sollten jedoch keine Netzwerkanrufe durchgeführt werden. Angular verwendet intern die XHRBackend
Klasse, um HTTP-Anforderungen XHRBackend
. Benutzer können dies überschreiben, um das Verhalten zu ändern. Das Angular- MockBackend
stellt MockBackend
und MockConnection
Klassen MockConnection
, mit denen HTTP-Anforderungen MockBackend
und MockConnection
werden können.
posts.service.ts
Dieser Dienst trifft einen Endpunkt der API, um eine Liste der Beiträge abzurufen.
import { Http } from '@angular/http'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs/rx'; import 'rxjs/add/operator/map'; export interface IPost { userId: number; id: number; title: string; body: string; } @Injectable() export class PostsService { posts: IPost[]; private postsUri = 'http://jsonplaceholder.typicode.com/posts'; constructor(private http: Http) { } get(): Observable<IPost[]> { return this.http.get(this.postsUri) .map((response) => response.json()); } }
posts.service.spec.ts
Hier werden wir den obigen Dienst testen, indem wir http api-Aufrufe posts.service.spec.ts
.
import { TestBed, inject, fakeAsync } from '@angular/core/testing'; import { HttpModule, XHRBackend, ResponseOptions, Response, RequestMethod } from '@angular/http'; import { MockBackend, MockConnection } from '@angular/http/testing'; import { PostsService } from './posts.service'; describe('PostsService', () => { // Mock http response const mockResponse = [ { 'userId': 1, 'id': 1, 'title': 'sunt aut facere repellat provident occaecati excepturi optio reprehenderit', 'body': 'quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto' }, { 'userId': 1, 'id': 2, 'title': 'qui est esse', 'body': 'est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla' }, { 'userId': 1, 'id': 3, 'title': 'ea molestias quasi exercitationem repellat qui ipsa sit aut', 'body': 'et iusto sed quo iure\nvoluptatem occaecati omnis eligendi aut ad\nvoluptatem doloribus vel accusantium quis pariatur\nmolestiae porro eius odio et labore et velit aut' }, { 'userId': 1, 'id': 4, 'title': 'eum et est occaecati', 'body': 'ullam et saepe reiciendis voluptatem adipisci\nsit amet autem assumenda provident rerum culpa\nquis hic commodi nesciunt rem tenetur doloremque ipsam iure\nquis sunt voluptatem rerum illo velit' } ]; beforeEach(() => { TestBed.configureTestingModule({ imports: [HttpModule], providers: [ { provide: XHRBackend, // This provides mocked XHR backend useClass: MockBackend }, PostsService ] }); }); it('should return posts retrieved from Api', fakeAsync( inject([XHRBackend, PostsService], (mockBackend, postsService) => { mockBackend.connections.subscribe( (connection: MockConnection) => { // Assert that service has requested correct url with expected method expect(connection.request.method).toBe(RequestMethod.Get); expect(connection.request.url).toBe('http://jsonplaceholder.typicode.com/posts'); // Send mock response connection.mockRespond(new Response(new ResponseOptions({ body: mockResponse }))); }); postsService.get() .subscribe((posts) => { expect(posts).toBe(mockResponse); }); }))); });
Winkelkomponenten testen - Basic
Der Komponentencode ist wie folgt angegeben.
import { Component } from '@angular/core';
@Component({
selector: 'my-app',
template: '<h1>{{title}}</h1>'
})
export class MyAppComponent{
title = 'welcome';
}
Für das Testen von Winkeln bietet angle seine Test-Dienstprogramme zusammen mit dem Test-Framework, das beim Schreiben des guten Testfalls in Winkel hilft. Angular-Dienstprogramme können aus @angular/core/testing
importiert @angular/core/testing
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MyAppComponent } from './banner-inline.component';
describe('Tests for MyAppComponent', () => {
let fixture: ComponentFixture<MyAppComponent>;
let comp: MyAppComponent;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [
MyAppComponent
]
});
});
beforeEach(() => {
fixture = TestBed.createComponent(MyAppComponent);
comp = fixture.componentInstance;
});
it('should create the MyAppComponent', () => {
expect(comp).toBeTruthy();
});
});
Im obigen Beispiel gibt es nur einen Testfall, der den Testfall für das Vorhandensein von Komponenten erklärt. Im obigen Beispiel werden Hilfsprogramme für die Winkelprüfung wie TestBed
und ComponentFixture
verwendet.
TestBed
wird zum Erstellen des Winkelprüfmoduls verwendet. Dieses Modul wird mit der configureTestingModule
Methode configureTestingModule
, um die Modulumgebung für die Klasse zu erstellen, die wir testen möchten. Testmodul, das vor der Ausführung jedes Testfalls konfiguriert werden muss. Deshalb konfigurieren wir das beforeEach
in der Funktion beforeEach
.
createComponent
Methode von TestBed
wird zum Erstellen der Instanz der zu createComponent
Komponente verwendet. createComponent
das ComponentFixture
. Das Fixture bietet Zugriff auf die Komponenteninstanz selbst.