You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在含await的Angular代码中使用HttpClientTestingModule测试?

问题

在给遗留Angular代码添加单元测试时发现,当代码使用await语法后,必须在每个httpTestingController.expect调用之间执行tick或flushMicrotask才能让测试通过。现有组件与测试代码如下:

组件代码

import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Component, OnInit } from '@angular/core';
import { firstValueFrom } from 'rxjs';

@Component({
  selector: 'app-t-comp',
  standalone: true,
  imports: [],
  templateUrl: './t-comp.component.html',
  styleUrl: './t-comp.component.scss'
})
export class TCompComponent implements OnInit {

  constructor(private http: HttpClient) {}

  ngOnInit(): void {
    // 该方法对应的测试可通过
    this.init();

    // 该方法对应的测试会失败,需处理异步时序
    // this.initAwaits();
  }

  init(): void {
    this.http.post('http://www.google.com', new HttpHeaders()).subscribe(googleResponse => {
      console.log(googleResponse);
      this.http.post('http://www.facebook.com', new HttpHeaders()).subscribe(facebookResponse => {
        console.log(facebookResponse);
      });
    });
  }

  async initAwaits(): Promise<void> {
    const googleResponse = await firstValueFrom(this.http.post('http://www.google.com', new HttpHeaders()));
    console.log(googleResponse);
    const facebookResponse = await firstValueFrom(this.http.post('http://www.facebook.com', new HttpHeaders()));
    console.log(facebookResponse);
  }
}

测试代码

import { ComponentFixture, TestBed } from '@angular/core/testing';

import { TCompComponent } from './t-comp.component';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';

describe('TCompComponent', () => {
  let component: TCompComponent;
  let fixture: ComponentFixture<TCompComponent>;
  let httpTestingController: HttpTestingController;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [TCompComponent, HttpClientTestingModule]
    })
      .compileComponents();

    fixture = TestBed.createComponent(TCompComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
    httpTestingController = TestBed.inject(HttpTestingController);
  });

  afterEach(() => {
    // 每个测试后断言无未处理请求
    httpTestingController.verify();
  });

  it('should create', () => {
    expect(component).toBeTruthy();

    httpTestingController.expectOne({
      method: 'POST',
      url: 'http://www.google.com',
    }, 'Google').flush({});

    // 若调用initAwaits,需将测试改为fakeAsync并调用tick/flush
    httpTestingController.expectOne({
      method: 'POST',
      url: 'http://www.facebook.com',
    }, 'Facebook').flush({});
  });
});

希望能规避在每个expect之间处理时序的操作,优先定义所有httpTestingController的断言,最后通过一次tick或flush让组件完成所有逻辑执行。

解决方案

以下两种方式可满足需求:

1. 使用fakeAsync + flush一次性处理异步流程

将测试逻辑包裹在fakeAsync中,先定义所有请求预期,再通过flush()一次性触发所有异步任务推进流程,无需在每个请求间单独处理时序。修改后的测试代码如下:

it('should create', fakeAsync(() => {
  expect(component).toBeTruthy();

  // 预先定义所有请求预期
  const googleReq = httpTestingController.expectOne({
    method: 'POST',
    url: 'http://www.google.com',
  }, 'Google');
  const facebookReq = httpTestingController.expectOne({
    method: 'POST',
    url: 'http://www.facebook.com',
  }, 'Facebook');

  // 响应第一个请求
  googleReq.flush({});
  // 触发所有待处理微任务,推进到第二个请求执行阶段
  flush();
  // 响应第二个请求
  facebookReq.flush({});

  httpTestingController.verify();
}));

flush()会清空所有待处理的微任务与宏任务,一次性将异步流程推进到完成状态,避免了多次调用tick的繁琐。

2. 用match预先匹配所有请求(推荐)

使用httpTestingController.match()批量匹配所有符合条件的请求,再统一处理响应,无需关心请求的执行顺序,仅需验证请求是否存在:

it('should create', fakeAsync(() => {
  expect(component).toBeTruthy();

  // 匹配所有POST请求
  const requests = httpTestingController.match(req => req.method === 'POST');
  
  // 根据URL区分并响应请求
  requests.forEach(req => {
    if (req.url === 'http://www.google.com') {
      req.flush({});
    } else if (req.url === 'http://www.facebook.com') {
      req.flush({});
    }
  });

  flush();
  httpTestingController.verify();
}));

这种方式灵活性更高,即使请求顺序发生变化,测试也能正常通过,适合复杂异步场景。

核心原理

await会将后续逻辑封装为微任务,在第一个请求flush后,必须触发微任务队列才能让组件执行第二个请求的逻辑。fakeAsync配合flush或tick()可一次性清空微任务队列,避免了在每个断言间单独处理时序的问题。

内容的提问来源于stack exchange,提问作者sdparker

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 16:08:14