Cómo usar waitForAsync y fakeAsync con Angular Testing

Introducción
Angular 2+ ofrece async
utilidades fakeAsync
para probar código asincrónico. Esto debería hacer que escribir pruebas unitarias y de integración de Angular sea mucho más fácil.
En este artículo, conocerá waitForAsync
algunas fakeAsync
pruebas de muestra.
Prerrequisitos
Para completar este tutorial, necesitarás:
- Node.js instalado localmente, lo cual puedes hacer siguiendo Cómo instalar Node.js y crear un entorno de desarrollo local.
- Algunos conocimientos sobre la configuración de un proyecto Angular.
Este tutorial fue verificado con Node v16.4.0, npm
v7.19.0 y @angular/core
v12.1.1.
Configuración del proyecto
Primero, use @angular/cli
para crear un nuevo proyecto:
- ng new angular-async-fakeasync-example
Luego, navegue hasta el directorio del proyecto recién creado:
- cd angular-async-fakeasync-example
Esto creará un nuevo proyecto Angular con archivos app.component.html
, app.compontent.ts
y app.component.spec.ts
.
Prueba conwaitForAsync
La waitForAsync
utilidad le indica a Angular que ejecute el código en una zona de prueba dedicada que intercepta las promesas. Tratamos brevemente la utilidad async en nuestra introducción a las pruebas unitarias en Angular cuando se usa compileComponents
.
La whenStable
utilidad nos permite esperar hasta que se hayan resuelto todas las promesas para ejecutar nuestras expectativas.
Primero abre y app.component.ts
usa Promise
a resolve
:title
src/app/app.component.ts
import { Component } from '@angular/core';@Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css']})export class AppComponent { title!: string; setTitle() { new Promise(resolve = { resolve('Async Title!'); }).then((val: any) = { this.title = val; }); }}
Luego ábrelo app.component.html
y reemplázalo con un h1
y button
:
src/aplicación/aplicación.componente.html
h1 {{ title }}/h1button (click)="setTitle()" Set Title/button
Cuando se hace clic en el botón, la title
propiedad se establece mediante una promesa.
Y así es como podemos probar esta funcionalidad usando waitForAsync
y whenStable
:
src/app/app.component.spec.ts
import { TestBed, waitForAsync } from '@angular/core/testing';import { By } from '@angular/platform-browser';import { AppComponent } from './app.component';describe('AppComponent', () = { beforeEach(async () = { await TestBed.configureTestingModule({ declarations: [ AppComponent ], }).compileComponents(); }); it('should display title', waitForAsync(() = { const fixture = TestBed.createComponent(AppComponent); fixture.debugElement .query(By.css('.set-title')) .triggerEventHandler('click', null); fixture.whenStable().then(() = { fixture.detectChanges(); const value = fixture.debugElement .query(By.css('h1')) .nativeElement .innerText; expect(value).toEqual('Async Title!'); }); }));});
Nota: En una aplicación real, tendrás promesas de que realmente esperan algo útil, como una respuesta de una solicitud a tu API de backend.
En este punto, puedes ejecutar tu prueba:
- ng test
Esto producirá un 'should display title'
resultado de prueba exitoso.
Prueba confakeAsync
El problema async
es que todavía tenemos que introducir una espera real en nuestras pruebas, y esto puede hacer que sean muy lentas. fakeAsync
viene al rescate y ayuda a probar código asincrónico de forma sincrónica.
Para demostrarlo fakeAsync
, comencemos con un ejemplo simple. Supongamos que nuestra plantilla de componente tiene un botón que incrementa un valor como este:
src/aplicación/aplicación.componente.html
h1 {{ incrementDecrement.value }}/h1button (click)="increment()" Increment/button
Llama a un increment
método en la clase del componente que se ve así:
src/app/app.component.ts
import { Component } from '@angular/core';import { IncrementDecrementService } from './increment-decrement.service';@Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css']})export class AppComponent { constructor(public incrementDecrement: IncrementDecrementService) { } increment() { this.incrementDecrement.increment(); }}
Y este método en sí mismo llama a un método en un incrementDecrement
servicio:
- ng generate service increment-decrement
Esto tiene un increment
método que se hace asincrónico con el uso de un setTimeout
:
src/app/incremento-decremento.servicio.ts
import { Injectable } from '@angular/core';@Injectable({ providedIn: 'root'})export class IncrementDecrementService { value = 0; message!: string; increment() { setTimeout(() = { if (this.value 15) { this.value += 1; this.message = ''; } else { this.message = 'Maximum reached!'; } }, 5000); // wait 5 seconds to increment the value }}
Obviamente, en una aplicación del mundo real esta asincronicidad se puede introducir de diferentes maneras.
Ahora usamos fakeAsync
la tick
utilidad para ejecutar una prueba de integración y asegurarnos de que el valor se incrementa en la plantilla:
src/app/app.component.spec.ts
import { TestBed, fakeAsync, tick } from '@angular/core/testing';import { By } from '@angular/platform-browser';import { AppComponent } from './app.component';describe('AppComponent', () = { beforeEach(async () = { await TestBed.configureTestingModule({ declarations: [ AppComponent ] }).compileComponents(); }); it('should increment in template after 5 seconds', fakeAsync(() = { const fixture = TestBed.createComponent(AppComponent); fixture.debugElement .query(By.css('button.increment')) .triggerEventHandler('click', null); tick(2000); fixture.detectChanges(); const value1 = fixture.debugElement.query(By.css('h1')).nativeElement.innerText; expect(value1).toEqual('0'); // value should still be 0 after 2 seconds tick(3000); fixture.detectChanges(); const value2 = fixture.debugElement.query(By.css('h1')).nativeElement.innerText; expect(value2).toEqual('1'); // 3 seconds later, our value should now be 1 }));});
Observe cómo tick
se utiliza la utilidad dentro de un fakeAsync
bloque para simular el paso del tiempo. El argumento que se pasa a tick
es la cantidad de milisegundos que deben pasar y estos son acumulativos dentro de una prueba.
Nota: Tick
también se puede utilizar sin argumentos, en cuyo caso espera hasta que se realicen todas las microtareas (cuando se resuelvan las promesas, por ejemplo).
En este punto, puedes ejecutar tu prueba:
- ng test
Esto producirá un 'should increment in template after 5 seconds'
resultado de prueba exitoso.
Especificar el tiempo de paso de esa manera puede volverse rápidamente complicado y convertirse en un problema cuando no se sabe cuánto tiempo debe pasar.
En Angular 4.2 se introdujo una nueva utilidad llamada flush
que ayuda con ese problema. Simula el paso del tiempo hasta que la cola de macrotareas se vacía. Las macrotareas incluyen cosas como setTimouts
, setIntervals
, y requestAnimationFrame
.
Entonces, usando flush
, podemos escribir una prueba como esta, por ejemplo:
src/app/app.component.spec.ts
import { TestBed, fakeAsync, flush } from '@angular/core/testing';import { By } from '@angular/platform-browser';import { AppComponent } from './app.component';describe('AppComponent', () = { beforeEach(async () = { await TestBed.configureTestingModule({ declarations: [ AppComponent ] }).compileComponents(); }); it('should increment in template', fakeAsync(() = { const fixture = TestBed.createComponent(AppComponent); fixture.debugElement .query(By.css('button.increment')) .triggerEventHandler('click', null); flush(); fixture.detectChanges(); const value = fixture.debugElement.query(By.css('h1')).nativeElement.innerText; expect(value).toEqual('1'); }));});
En este punto, puedes ejecutar tu prueba:
- ng test
Esto producirá un 'should increment in template'
resultado de prueba exitoso.
Conclusión
waitForAsync
En este artículo le presentamos fakeAsync
ejemplos de pruebas.
También puedes consultar la documentación oficial para obtener una guía detallada sobre pruebas de Angular.
Deja una respuesta