Angular 2
Test di un'app Angular 2
Ricerca…
Installazione del framework di test Jasmine
Il modo più comune per testare le app Angular 2 è con il framework di test Jasmine. Jasmine ti permette di testare il tuo codice nel browser.
Installare
Per iniziare, tutto ciò che serve è il pacchetto jasmine-core
(non jasmine
).
npm install jasmine-core --save-dev --save-exact
Verificare
Per verificare che Jasmine sia configurato correttamente, crea il file ./src/unit-tests.html
con il seguente contenuto e aprilo nel 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>
Impostazione dei test con Gulp, Webpack, Karma e Jasmine
La prima cosa di cui abbiamo bisogno è di dire al karma di usare Webpack per leggere i nostri test, in una configurazione che abbiamo impostato per il motore del webpack. Qui, sto usando Babele perché scrivo il mio codice in ES6, puoi cambiarlo per altri gusti, come Typescript. O io uso i modelli Pug (precedentemente Jade), non è necessario.
Tuttavia, la strategia rimane la stessa.
Quindi, questa è una configurazione del webpack:
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;
E poi, abbiamo bisogno di un file karma.config.js per usare quella configurazione di webpack:
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
});
};
Finora, abbiamo detto a Karma di usare il webpack e gli abbiamo detto di iniziare da un file chiamato karma.shim.js . questo file avrà il compito di fungere da punto di partenza per il webpack. webpack leggerà questo file e userà le istruzioni di importazione e richiesta per raccogliere tutte le nostre dipendenze ed eseguire i nostri test.
Quindi ora, diamo un'occhiata al file 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);
In sostanza, stiamo importando TestBed dal test del core angolare e avviando l'ambiente, poiché deve essere avviato solo una volta per tutti i nostri test. Quindi, stiamo esaminando la directory src / app in modo ricorsivo e leggendo tutti i file che terminano con .spec.js e li inoltriamo a testContext, quindi verranno eseguiti.
Di solito cerco di mettere i miei test nello stesso posto della classe. Sapore personale, mi rende più facile importare dipendenze e refactoring con le classi. Ma se vuoi mettere i tuoi test da qualche altra parte, ad esempio sotto la directory src / test, ad esempio, ecco la possibilità. cambia la riga prima dell'ultima nel file karma.shim.js.
Perfezionare. cos'è rimasto? ah, il compito di gulp che usa il file karma.config.js che abbiamo fatto sopra:
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();
});
Ora sto avviando il server con il file di configurazione che abbiamo creato, dicendogli di eseguirlo una volta e di non controllare le modifiche. Trovo questo per me meglio come i test verranno eseguiti solo se sono pronto per loro di correre, ma ovviamente se vuoi che tu sappia dove cambiare.
E come esempio finale del codice, ecco una serie di test per il tutorial di Angular 2, "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");
})
));
});
Degno di nota in questo è il modo in cui abbiamo primaOach () configurare un modulo di test e creare il componente in test, e come chiamiamo detectChanges () in modo che l'angolare attraversi effettivamente il doppio legame e tutto.
Si noti che ogni test è una chiamata a async () e attende sempre che whenStable prometta di risolvere prima di esaminare l'apparecchiatura. Quindi ha accesso al componente attraverso componentInstance e all'elemento tramite nativeElement .
C'è un test che sta controllando lo stile corretto. come parte del Tutorial, il team di Angular dimostra l'uso degli stili all'interno dei componenti. Nel nostro test, usiamo getComputedStyle () per verificare che gli stili vengano da dove abbiamo specificato, tuttavia abbiamo bisogno dell'oggetto Window per quello, e lo stiamo ottenendo dall'elemento come potete vedere nel test.
Test del servizio Http
Solitamente, i servizi chiamano Api remoti per recuperare / inviare dati. Ma i test unitari non dovrebbero effettuare chiamate di rete. Angular utilizza XHRBackend
classe XHRBackend
per fare richieste http. L'utente può sovrascriverlo per modificare il comportamento. Il modulo di test angolare fornisce classi MockBackend
e MockConnection
che possono essere utilizzate per testare e far valere richieste http.
posts.service.ts
Questo servizio raggiunge un endpoint api per recuperare l'elenco dei post.
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
Qui, posts.service.spec.ts
servizio di cui sopra posts.service.spec.ts
giro le chiamate http api.
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); }); }))); });
Test di componenti angolari - Di base
Il codice componente è dato come di seguito.
import { Component } from '@angular/core';
@Component({
selector: 'my-app',
template: '<h1>{{title}}</h1>'
})
export class MyAppComponent{
title = 'welcome';
}
Per il test angolare, l'angolare fornisce le sue utilità di test insieme al framework di test che aiuta a scrivere il caso di test positivo in angolare. Le utility angolari possono essere importate da @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();
});
});
Nell'esempio sopra, c'è solo un caso di test che spiega il caso di test per l'esistenza del componente. Nell'esempio sopra TestBed
vengono utilizzate utility di test angolari come TestBed
e ComponentFixture
.
TestBed
viene utilizzato per creare il modulo di test angolare e configuriamo questo modulo con il metodo configureTestingModule
per produrre l'ambiente del modulo per la classe che vogliamo testare. Modulo di test da configurare prima dell'esecuzione di ogni caso di test è per questo che configuriamo il modulo di test nella funzione beforeEach
.
createComponent
metodo createComponent
di TestBed
viene utilizzato per creare l'istanza del componente in prova. createComponent
restituisce ComponentFixture
. L'apparecchiatura fornisce l'accesso all'istanza del componente stessa.