Angular 2
Probando una aplicación Angular 2
Buscar..
Instalando el framework de pruebas Jasmine
La forma más común de probar aplicaciones de Angular 2 es con el marco de prueba Jasmine. Jasmine te permite probar tu código en el navegador.
Instalar
Para comenzar, todo lo que necesita es el paquete jasmine-core
(no jasmine
).
npm install jasmine-core --save-dev --save-exact
Verificar
Para verificar que Jasmine está configurado correctamente, cree el archivo ./src/unit-tests.html
con el siguiente contenido y ábralo en el navegador.
<!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>
Configurando pruebas con Gulp, Webpack, Karma y Jasmine
Lo primero que necesitamos es decirle a karma que use Webpack para leer nuestras pruebas, bajo una configuración que configuramos para el motor del paquete web. Aquí, estoy usando babel porque escribo mi código en ES6, puedes cambiarlo por otros sabores, como Typescript. O uso plantillas Pug (anteriormente Jade), no tienes que hacerlo.
Aún así, la estrategia sigue siendo la misma.
Por lo tanto, esta es una configuración 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;
Y luego, necesitamos un archivo karma.config.js para usar esa configuración de 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
});
};
Hasta ahora, le hemos dicho a Karma que use el paquete web, y le hemos dicho que comience en un archivo llamado karma.shim.js . Este archivo tendrá el trabajo de actuar como punto de partida para el paquete web. webpack leerá este archivo y utilizará las declaraciones de importación y requerimiento para reunir todas nuestras dependencias y ejecutar nuestras pruebas.
Así que ahora, veamos el archivo 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);
En esencia, estamos importando TestBed desde las pruebas de núcleo angular e iniciando el entorno, ya que debe iniciarse solo una vez para todas nuestras pruebas. Luego, vamos a través del directorio src / app recursivamente y leemos cada archivo que termina con .spec.js y los alimentamos a testContext, por lo que se ejecutarán.
Usualmente trato de poner mis exámenes en el mismo lugar que la clase. A gusto personal, me facilita importar dependencias y refactorizar pruebas con clases. Pero si desea poner sus pruebas en otro lugar, como en el directorio src / test , por ejemplo, aquí tiene la oportunidad. cambia la línea antes del último en el archivo karma.shim.js.
Perfecto. ¿lo que queda? ah, la tarea truculenta que usa el archivo karma.config.js que hicimos anteriormente:
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();
});
Ahora estoy iniciando el servidor con el archivo de configuración que creamos, diciéndole que se ejecute una vez y que no observe los cambios. Considero que esto se adapta mejor a mí, ya que las pruebas solo se ejecutarán si estoy listo para que se ejecuten, pero, por supuesto, si quieres algo diferente, sabes dónde cambiar.
Y como ejemplo de código final, aquí hay un conjunto de pruebas para el tutorial de 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");
})
));
});
Cabe destacar cómo tenemos antes que cada () configurar un módulo de prueba y crear el componente en prueba, y cómo llamamos detectChanges () para que el ángulo pase por el enlace doble y todo.
Tenga en cuenta que cada prueba es una llamada a async () y siempre espera a que la promesa de WhenStable se resuelva antes de examinar el dispositivo. Luego tiene acceso al componente a través de componentInstance y al elemento a través de nativeElement .
Hay una prueba que está comprobando el estilo correcto. como parte del Tutorial, el equipo de Angular demuestra el uso de estilos dentro de los componentes. En nuestra prueba, usamos getComputedStyle () para verificar que los estilos provienen de donde especificamos, sin embargo, necesitamos el objeto Window para eso, y lo obtenemos del elemento como se puede ver en la prueba.
Servicio de prueba HTTP
Por lo general, los servicios llaman a Api remoto para recuperar / enviar datos. Pero las pruebas unitarias no deberían hacer llamadas de red. Angular utiliza XHRBackend
clase XHRBackend
para hacer solicitudes http. El usuario puede anular esto para cambiar el comportamiento. El módulo de prueba angular proporciona las clases MockBackend
y MockConnection
que se pueden usar para probar y afirmar las solicitudes http.
posts.service.ts
Este servicio llega a un punto final de API para recuperar la lista de publicaciones.
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
Aquí, posts.service.spec.ts
servicio anterior posts.service.spec.ts
llamadas a la API de HTTP.
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); }); }))); });
Pruebas de componentes angulares - Básico
El código del componente se da a continuación.
import { Component } from '@angular/core';
@Component({
selector: 'my-app',
template: '<h1>{{title}}</h1>'
})
export class MyAppComponent{
title = 'welcome';
}
Para la prueba angular, angular proporciona sus utilidades de prueba junto con el marco de prueba que ayuda a escribir el buen caso de prueba en angular. Las utilidades angulares se pueden importar desde @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();
});
});
En el ejemplo anterior, solo hay un caso de prueba que explica el caso de prueba para la existencia de componentes. En el ejemplo anterior, se utilizan utilidades de prueba angular como TestBed
y ComponentFixture
.
TestBed
se utiliza para crear el módulo de prueba angular y configuramos este módulo con el método configureTestingModule
para producir el entorno de módulo para la clase que queremos probar. El módulo de prueba debe configurarse antes de la ejecución de cada caso de prueba, por eso configuramos el módulo de prueba en la función beforeEach
.
createComponent
método createComponent
de TestBed
se utiliza para crear la instancia del componente bajo prueba. createComponent
devuelve el ComponentFixture
. El accesorio proporciona acceso a la instancia del componente en sí.