如何在 Angular 测试中使用 waitForAsync 和 fakeAsync

简介

Angular 2+提供了fakeAsyncfakeAsync工具来测试异步代码。这应该会让你的Angular单元和集成测试更容易编写。

在本文中,您将通过示例测试介绍waitForAsyncfakeAsync

前提条件

要完成本教程,您需要:

本教程已经在Node v16.4.0、npmv7.19.0和@angular/corev12.1.1上进行了验证。

设置项目

首先,使用@angular/cli创建一个新项目:

1ng new angular-async-fakeasync-example

然后,导航到新创建的项目目录:

1cd angular-async-fakeasync-example

这将创建一个新的角度项目,其中包含app.Component.htmlapp.Component.tsapp.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,替换为h1Button

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属性。

下面是我们如何使用waitForAsyncwhenStable测试此功能:

 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}

显然,在现实世界的应用程序中,可以通过许多不同的方式引入这种异步性。

现在让我们使用fakeAsynctick实用程序来运行集成测试,并确保该值在模板中递增:

 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的新实用程序,它可以帮助解决这个问题。它模拟宏任务队列为空之前的时间流逝。宏任务包括setTimoutssetIntervalsquestAnimationFrame等。

因此,使用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

这将在模板‘测试结果中生成成功的’应递增‘’。

结论

本文通过示例测试向您介绍了waitForAsyncfakeAsync

你也可以参考官方文档获取深入的Angular测试指南。

Published At
Categories with 技术
Tagged with
comments powered by Disqus