Cómo usar waitForAsync y fakeAsync con Angular Testing

Introducción
Angular 2+ ofrece asyncutilidades fakeAsyncpara 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á waitForAsyncalgunas fakeAsyncpruebas 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, npmv7.19.0 y @angular/corev12.1.1.
Configuración del proyecto
Primero, use @angular/clipara 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.tsy app.component.spec.ts.
Prueba conwaitForAsync
La waitForAsyncutilidad 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 whenStableutilidad nos permite esperar hasta que se hayan resuelto todas las promesas para ejecutar nuestras expectativas.
Primero abre y app.component.tsusa Promisea 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.htmly reemplázalo con un h1y 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 titlepropiedad se establece mediante una promesa.
Y así es como podemos probar esta funcionalidad usando waitForAsyncy 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 asynces que todavía tenemos que introducir una espera real en nuestras pruebas, y esto puede hacer que sean muy lentas. fakeAsyncviene 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 incrementmé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 incrementDecrementservicio:
- ng generate service increment-decrement
Esto tiene un incrementmé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 fakeAsyncla tickutilidad 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 tickse utiliza la utilidad dentro de un fakeAsyncbloque para simular el paso del tiempo. El argumento que se pasa a tickes 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 flushque 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
waitForAsyncEn este artículo le presentamos fakeAsyncejemplos de pruebas.
También puedes consultar la documentación oficial para obtener una guía detallada sobre pruebas de Angular.

Deja una respuesta