介绍
角2+提供async
和fakeAsync
用于测试异步代码工具。这应该会让你的 Angular 单元和集成测试更容易编写。
在本文中,您将了解waitForAsync
和fakeAsync
使用示例测试。
先决条件
要完成本教程,您需要:
- Node.js 安装在本地,您可以按照如何安装 Node.js 和创建本地开发环境来完成。
- 对设置 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
文件。
测试 waitForAsync
该waitForAsync
实用程序告诉 Angular 在拦截承诺的专用测试区中运行代码。我们在 Angular 中使用单元测试的介绍中简要介绍了异步实用程序compileComponents
。
该whenStable
实用程序允许我们等到所有承诺都得到解决才能运行我们的期望。
首先打开app.component.ts
并使用Promise
到resolve
的title
:
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
:
<h1>
{{ title }}
</h1>
<button (click)="setTitle()" class="set-title">
Set Title
</button>
单击按钮时,title
将使用承诺设置该属性。
以下是我们如何使用waitForAsync
and测试此功能的方法whenStable
:
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
,让我们从一个简单的例子开始。假设我们的组件模板有一个按钮,它增加一个值,如下所示:
<h1>
{{ incrementDecrement.value }}
</h1>
<button (click)="increment()" class="increment">
Increment
</button>
它调用increment
组件类中的一个方法,如下所示:
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
使用 a 异步的方法setTimeout
:
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
实用程序运行集成测试并确保该值在模板中递增:
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
也可以不带参数使用,在这种情况下,它会等待所有微任务完成(例如,当 promise 被解析时)。
此时,您可以运行您的测试:
- ng test
这将产生一个成功的'should increment in template after 5 seconds'
测试结果。
像这样指定经过的时间很快就会变得很麻烦,并且当您不知道应该经过多少时间时可能会成为一个问题。
flush
Angular 4.2 中引入了一个名为的新实用程序,有助于解决该问题。它模拟时间流逝,直到宏任务队列为空。宏任务包括像setTimouts
,setIntervals
和requestAnimationFrame
。
因此,使用flush
,我们可以编写这样的测试,例如:
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 测试指南。