Angular 2
Testa en Angular 2-app
Sök…
Installera Jasmine-testramen
Det vanligaste sättet att testa Angular 2-appar är med Jasmine-testramen. Jasmine låter dig testa din kod i webbläsaren.
Installera
För att komma igång är allt du behöver jasmine-core
kärnpaketet (inte jasmine
).
npm install jasmine-core --save-dev --save-exact
Kontrollera
För att verifiera att Jasmine är korrekt ./src/unit-tests.html
skapar du filen ./src/unit-tests.html
med följande innehåll och öppnar den i webbläsaren.
<!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>
Starta tester med Gulp, Webpack, Karma och Jasmine
Det första vi behöver är att berätta för karma att använda Webpack för att läsa våra tester, under en konfiguration som vi ställt in för webbpackmotorn. Här använder jag babel eftersom jag skriver min kod i ES6, du kan ändra den för andra smaker, till exempel Typescript. Eller så använder jag Pug (tidigare Jade) mallar, det behöver du inte.
Strategin är fortfarande densamma.
Så detta är en webbpakkonfigur:
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;
Och sedan behöver vi en karma.config.js-fil för att använda den webbpakkonfigur:
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
});
};
Hittills har vi berättat för Karma att använda webpack, och vi har sagt att den ska börja med en fil som heter karma.shim.js . denna fil har jobbet att fungera som utgångspunkt för webbpakke. webpack kommer att läsa den här filen och använda importen och kräva uttalanden för att samla alla våra beroenden och köra våra tester.
Så nu, låt oss titta på filen karma.shim.js:
// 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);
I huvudsak importerar vi TestBed från vinkellkärntestning och initierar miljön, eftersom det bara måste initieras en gång för alla våra tester. Sedan går vi igenom src / app- katalogen rekursivt och läser alla filer som slutar med .spec.js och matar dem till testContext så att de kommer att köras.
Jag försöker vanligtvis lägga mina tester på samma plats som klassen. Personlig smak, det gör det lättare för mig att importera beroenden och refaktortester med klasser. Men om du vill lägga dina tester någon annanstans, som till exempel under src / testkatalogen , här är du chans. ändra raden före sist i filen karma.shim.js.
Perfekt. vad är kvar? ah, gulp-uppgiften som använder karma.config.js-filen som vi gjorde ovan:
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();
});
Jag startar nu servern med konfigurationsfilen som vi skapade, berättar att den ska köras en gång och inte leta efter förändringar. Jag tycker att detta passar mig bättre eftersom testerna bara kommer att köras om jag är redo för att de ska köras, men naturligtvis om du vill ha olika vet du var du ska ändra.
Och som mitt sista kodprov, här är en uppsättning tester för Angular 2-tutorialen, "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");
})
));
});
Det är anmärkningsvärt hur vi har konfigurerat en testmodul innanEch () och skapar komponenten i testet, och hur vi kallar detectChanges () så att vinkeln faktiskt går igenom dubbelbindningen och allt.
Lägg märke till att varje test är ett samtal till async () och det väntar alltid på när Stable lovar att lösa innan man undersöker fixturen. Den har sedan åtkomst till komponenten via componentInstance och till elementet via nativeElement .
Det finns ett test som kontrollerar rätt styling. som en del av självstudien demonstrerar Angular team användning av stilar inuti komponenter. I vårt test använder vi getComputedStyle () för att kontrollera att stilar kommer där vi specificerade, men vi behöver Window-objektet för det, och vi får det från elementet som du kan se i testet.
Testa Http-tjänsten
Vanligtvis ringer tjänster fjärr Api för att hämta / skicka data. Men enhetstester bör inte göra nätverkssamtal. Angular använder internt XHRBackend
klassen för att göra http-förfrågningar. Användaren kan åsidosätta detta för att ändra beteende. MockBackend
tillhandahåller MockBackend
och MockConnection
klasser som kan användas för att testa och hävda http-förfrågningar.
posts.service.ts
här tjänsten träffar en api-slutpunkt för att hämta lista över inlägg.
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
Här posts.service.spec.ts
vi tjänsten ovan genom att håna http api-samtal.
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); }); }))); });
Testa vinkelkomponenter - grundläggande
Komponentkoden anges enligt nedan.
import { Component } from '@angular/core';
@Component({
selector: 'my-app',
template: '<h1>{{title}}</h1>'
})
export class MyAppComponent{
title = 'welcome';
}
För vinkeltestning tillhandahåller vinkeln sina testverktyg tillsammans med testramen som hjälper till att skriva det goda testfallet i vinkel. Vinkelverktyg kan importeras från @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();
});
});
I exemplet ovan finns det bara ett testfall som förklarar testfallet för komponentexistens. I exemplet ovan används TestBed
som TestBed
och ComponentFixture
.
TestBed
används för att skapa vinkeltestmodulen och vi konfigurerar denna modul med metoden configureTestingModule
för att producera modulmiljön för den klass vi vill testa. Testmodul som ska konfigureras innan varje testfall utförs. Därför konfigurerar vi beforeEach
funktionen beforeEach
.
createComponent
metoden för TestBed
används för att skapa förekomsten av komponenten som testas. createComponent
returnerar ComponentFixture
. Fixturen ger åtkomst till själva komponentinstansen.