简介
Angular 2+提供了fakeAsync
和fakeAsync
工具来测试异步代码。这应该会让你的Angular单元和集成测试更容易编写。
在本文中,您将通过示例测试介绍waitForAsync
和fakeAsync
。
前提条件
要完成本教程,您需要:
- 本地安装node.js,可按照如何安装node.js并创建本地开发Environment.
- 对设置角度project.》有所了解
本教程已经在Node v16.4.0、npm
v7.19.0和@angular/core
v12.1.1上进行了验证。
设置项目
首先,使用@angular/cli
创建一个新项目:
1ng new angular-async-fakeasync-example
然后,导航到新创建的项目目录:
1cd angular-async-fakeasync-example
这将创建一个新的角度项目,其中包含app.Component.html
、app.Component.ts
和app.Component.spec.ts
文件。
waitForAsync
测试
waitForAsync
实用程序告诉ANGLE在拦截承诺的专用测试区中运行代码。我们在单位testing简介》中简要介绍了使用编译组件
时以角度表示的异步实用程序。
whenStable
实用程序允许我们等待,直到所有的promise都被解决,以运行我们的期望。
首先打开app.Component.ts
,并使用Promise
来解析
标题`:
1[label src/app/app.component.ts]
2import { Component } from '@angular/core';
3
4@Component({
5 selector: 'app-root',
6 templateUrl: './app.component.html',
7 styleUrls: ['./app.component.css']
8})
9export class AppComponent {
10 title!: string;
11
12 setTitle() {
13 new Promise(resolve => {
14 resolve('Async Title!');
15 }).then((val: any) => {
16 this.title = val;
17 });
18 }
19}
然后打开app.Component.html
,替换为h1
和Button
:
1[label src/app/app.component.html]
2<h1>
3 {{ title }}
4</h1>
5
6<button (click)="setTitle()" class="set-title">
7 Set Title
8</button>
单击该按钮时,将使用Promise设置tile
属性。
下面是我们如何使用waitForAsync
和whenStable
测试此功能:
1[label src/app/app.component.spec.ts]
2import { TestBed, waitForAsync } from '@angular/core/testing';
3import { By } from '@angular/platform-browser';
4import { AppComponent } from './app.component';
5
6describe('AppComponent', () => {
7 beforeEach(async () => {
8 await TestBed.configureTestingModule({
9 declarations: [
10 AppComponent
11 ],
12 }).compileComponents();
13 });
14
15 it('should display title', waitForAsync(() => {
16 const fixture = TestBed.createComponent(AppComponent);
17
18 fixture.debugElement
19 .query(By.css('.set-title'))
20 .triggerEventHandler('click', null);
21
22 fixture.whenStable().then(() => {
23 fixture.detectChanges();
24 const value = fixture.debugElement
25 .query(By.css('h1'))
26 .nativeElement
27 .innerText;
28 expect(value).toEqual('Async Title!');
29 });
30 }));
31});
<$>[备注] 注意: 在真正的应用程序中,你会得到一些承诺,这些承诺实际上会等待一些有用的东西,比如对后端API的请求响应。 <$>
此时,您可以运行测试:
1ng test
这将产生一个成功的`‘应该显示标题’‘测试结果。
fakeAsync
测试
异步
的问题是,我们仍然必须在测试中引入真正的等待,这可能会使我们的测试非常慢。fakeAsync
是救命稻草,它帮助以同步的方式测试异步代码。
为了演示fakeAsync
,让我们从一个简单的例子开始。假设我们的组件模板有一个按钮,可以像这样增加一个值:
1[label src/app/app.component.html]
2<h1>
3 {{ incrementDecrement.value }}
4</h1>
5
6<button (click)="increment()" class="increment">
7 Increment
8</button>
它调用Component类中的increment
方法,如下所示:
1[label src/app/app.component.ts]
2import { Component } from '@angular/core';
3import { IncrementDecrementService } from './increment-decrement.service';
4
5@Component({
6 selector: 'app-root',
7 templateUrl: './app.component.html',
8 styleUrls: ['./app.component.css']
9})
10export class AppComponent {
11 constructor(public incrementDecrement: IncrementDecrementService) { }
12
13 increment() {
14 this.incrementDecrement.increment();
15 }
16}
此方法本身调用incrementDecrement
服务中的方法:
1ng generate service increment-decrement
它有一个通过使用setTimeout
使其异步的increment
方法:
1[label src/app/increment-decrement.service.ts]
2import { Injectable } from '@angular/core';
3
4@Injectable({
5 providedIn: 'root'
6})
7export class IncrementDecrementService {
8 value = 0;
9 message!: string;
10
11 increment() {
12 setTimeout(() => {
13 if (this.value < 15) {
14 this.value += 1;
15 this.message = '';
16 } else {
17 this.message = 'Maximum reached!';
18 }
19 }, 5000); // wait 5 seconds to increment the value
20 }
21}
显然,在现实世界的应用程序中,可以通过许多不同的方式引入这种异步性。
现在让我们使用fakeAsync
和tick
实用程序来运行集成测试,并确保该值在模板中递增:
1[label src/app/app.component.spec.ts]
2import { TestBed, fakeAsync, tick } from '@angular/core/testing';
3import { By } from '@angular/platform-browser';
4import { AppComponent } from './app.component';
5
6describe('AppComponent', () => {
7 beforeEach(async () => {
8 await TestBed.configureTestingModule({
9 declarations: [
10 AppComponent
11 ]
12 }).compileComponents();
13 });
14
15 it('should increment in template after 5 seconds', fakeAsync(() => {
16 const fixture = TestBed.createComponent(AppComponent);
17
18 fixture.debugElement
19 .query(By.css('button.increment'))
20 .triggerEventHandler('click', null);
21
22 tick(2000);
23
24 fixture.detectChanges();
25 const value1 = fixture.debugElement.query(By.css('h1')).nativeElement.innerText;
26 expect(value1).toEqual('0'); // value should still be 0 after 2 seconds
27
28 tick(3000);
29
30 fixture.detectChanges();
31 const value2 = fixture.debugElement.query(By.css('h1')).nativeElement.innerText;
32 expect(value2).toEqual('1'); // 3 seconds later, our value should now be 1
33 }));
34});
请注意tick
实用程序是如何在fakeAsync
块内用于模拟时间流逝的。传入tick
的参数是要传递的毫秒数,这些参数在一次测试中是累加的。
<$>[备注]
注: Tick
也可以不带参数使用,等待所有微任务完成(例如承诺解析)。
<$>
此时,您可以运行测试:
1ng test
这将产生一个成功的``应该在5秒后在模板中递增‘`测试结果。
像这样浪费时间很快就会变得很麻烦,当你不知道应该浪费多少时间时,这会成为一个问题。
在角度4.2中引入了一个名为flush
的新实用程序,它可以帮助解决这个问题。它模拟宏任务队列为空之前的时间流逝。宏任务包括setTimouts
、setIntervals
、questAnimationFrame
等。
因此,使用flush
,我们可以编写如下测试,例如:
1[label src/app/app.component.spec.ts]
2import { TestBed, fakeAsync, flush } from '@angular/core/testing';
3import { By } from '@angular/platform-browser';
4import { AppComponent } from './app.component';
5
6describe('AppComponent', () => {
7 beforeEach(async () => {
8 await TestBed.configureTestingModule({
9 declarations: [
10 AppComponent
11 ]
12 }).compileComponents();
13 });
14
15 it('should increment in template', fakeAsync(() => {
16 const fixture = TestBed.createComponent(AppComponent);
17
18 fixture.debugElement
19 .query(By.css('button.increment'))
20 .triggerEventHandler('click', null);
21
22 flush();
23 fixture.detectChanges();
24
25 const value = fixture.debugElement.query(By.css('h1')).nativeElement.innerText;
26 expect(value).toEqual('1');
27 }));
28});
在这里,你可以运行你的测试:
1ng test
这将在模板‘测试结果中生成成功的
’应递增‘’。
结论
本文通过示例测试向您介绍了waitForAsync
和fakeAsync
。
你也可以参考官方文档获取深入的Angular测试指南。