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

来自菜鸟教程
跳转至:导航、​搜索

介绍

Angular 2+ 提供 asyncfakeAsync 实用程序来测试异步代码。 这应该使您的 Angular 单元和集成测试更容易编写。

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

先决条件

要完成本教程,您需要:

  • 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.htmlapp.compontent.tsapp.component.spec.ts 文件的新 Angular 项目。

使用 waitForAsync 进行测试

waitForAsync 实用程序告诉 Angular 在拦截 Promise 的专用测试区域中运行代码。 在使用 compileComponents 时,我们在 Angular 中的单元测试 简介中简要介绍了异步实用程序。

whenStable 实用程序允许我们等到所有的 Promise 都解决后才能运行我们的期望。

首先打开 app.component.ts 并使用 Promiseresolve 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 并用 h1button 替换它:

src/app/app.component.html

<h1>
  {{ title }}
</h1>

<button (click)="setTitle()" class="set-title">
  Set Title
</button>

单击按钮时,title 属性使用承诺设置。

以下是我们如何使用 waitForAsyncwhenStable 测试此功能的方法:

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
  }
}

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

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

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 的新实用程序,并有助于解决该问题。 它模拟时间的流逝,直到宏任务队列为空。 宏任务包括 setTimoutssetIntervalsrequestAnimationFrame 等。

因此,使用 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' 测试结果。

结论

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

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