如何在Angular测试中使用waitForAsync和fakeAsync
介绍
Angular 2+ 提供 async
和 fakeAsync
实用程序来测试异步代码。 这应该使您的 Angular 单元和集成测试更容易编写。
在本文中,您将通过示例测试向您介绍waitForAsync
和fakeAsync
。
先决条件
要完成本教程,您需要:
- Node.js 安装在本地,您可以按照【X57X】如何安装Node.js 并创建本地开发环境【X126X】进行。
- 熟悉 设置 Angular 项目 。
本教程已使用 Node v16.4.0、npm
v7.19.0 和 @angular/core
v12.1.1 进行了验证。
设置项目
首先,使用 @angular/cli
创建一个新项目:
ng new angular-async-fakeasync-example
然后,导航到新创建的项目目录:
cd angular-async-fakeasync-example
这将创建一个包含 app.component.html
、app.compontent.ts
和 app.component.spec.ts
文件的新 Angular 项目。
使用 waitForAsync
进行测试
waitForAsync
实用程序告诉 Angular 在拦截 Promise 的专用测试区域中运行代码。 在使用 compileComponents
时,我们在 Angular 中的单元测试 简介中简要介绍了异步实用程序。
whenStable
实用程序允许我们等到所有的 Promise 都解决后才能运行我们的期望。
首先打开 app.component.ts
并使用 Promise
到 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; }); } }
然后打开 app.component.html
并用 h1
和 button
替换它:
src/app/app.component.html
<h1> {{ title }} </h1> <button (click)="setTitle()" class="set-title"> Set Title </button>
单击按钮时,title
属性使用承诺设置。
以下是我们如何使用 waitForAsync
和 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!'); }); })); });
注意: 在一个真实的应用程序中,您将拥有实际等待有用的东西的承诺,例如对后端 API 的请求的响应。
此时,您可以运行测试:
ng test
这将产生一个成功的 'should display title'
测试结果。
使用 fakeAsync
进行测试
async
的问题是我们仍然必须在测试中引入真正的等待,这会使我们的测试变得非常慢。 fakeAsync
来拯救并帮助以同步方式测试异步代码。
为了演示fakeAsync
,让我们从一个简单的例子开始。 假设我们的组件模板有一个按钮,可以像这样增加一个值:
src/app/app.component.html
<h1> {{ incrementDecrement.value }} </h1> <button (click)="increment()" class="increment"> Increment </button>
它调用组件类中的 increment
方法,如下所示:
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(); } }
这个方法本身调用了 incrementDecrement
服务中的一个方法:
ng generate service increment-decrement
它有一个 increment
方法,它通过使用 setTimeout
实现异步:
src/app/increment-decrement.service.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 } }
显然,在现实世界的应用程序中,这种异步性可以通过多种不同的方式引入。
现在让我们使用 fakeAsync
和 tick
实用程序来运行集成测试,并确保模板中的值递增:
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 })); });
请注意 tick
实用程序如何在 fakeAsync
块内用于模拟时间的流逝。 传入 tick
的参数是通过的毫秒数,这些在测试中是累积的。
笔记: Tick
can also be used with no argument, in which case it waits until all the microtasks are done (when promises are resolved for example).
此时,您可以运行测试:
ng test
这将产生一个成功的 'should increment in template after 5 seconds'
测试结果。
像这样指定经过时间会很快变得很麻烦,并且当您不知道应该经过多少时间时可能会成为问题。
Angular 4.2 中引入了一个名为 flush
的新实用程序,并有助于解决该问题。 它模拟时间的流逝,直到宏任务队列为空。 宏任务包括 setTimouts
、setIntervals
和 requestAnimationFrame
等。
因此,使用 flush
,我们可以编写这样的测试,例如:
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'); })); });
此时,您可以运行测试:
ng test
这将产生一个成功的 'should increment in template'
测试结果。
结论
在本文中,通过示例测试向您介绍了 waitForAsync
和 fakeAsync
。
您也可以参考 官方文档 以获得深入的 Angular 测试指南。