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

Angular测试报错排查:fakeAsync测试XHR异常及done.fail错误

问题分析与正确测试写法

我来帮你拆解这两个错误的根源,再给出能跑通的测试代码:

第一个错误:Cannot make XHRs from within a fake async test

这个报错的核心是你的Http请求没被MockBackend成功拦截,导致测试偷偷发了真实的网络请求,而fakeAsync环境是禁止真实异步网络操作的。

大概率是你在测试模块里没正确配置Http的提供者——你得告诉Angular:“别用真实的Http,用我用MockBackend造的假的!”。另外,MockBackend的回调是微任务,有时候tick()不一定能触发,用flushMicrotasks()更靠谱。

第二个错误:TypeError: done.fail is not a function

这是因为你把inject、async和done的用法搞混了:

  • async本身就会处理测试里的异步逻辑,根本不需要手动传done回调;
  • 你把inject的调用塞进done里直接执行,导致async内部的错误处理找不到正确的done.fail方法,自然报错。

正确的测试代码(两种方式可选)

方式1:用fakeAsync + flushMicrotasks(推荐,更直观)

首先要确保测试模块配置正确,然后写测试逻辑:

import { TestBed, fakeAsync, inject, flushMicrotasks } from '@angular/core/testing';
import { ProductService } from './product.service';
import { MockBackend, MockConnection } from '@angular/http/testing';
import { Http, BaseRequestOptions, Response, ResponseOptions } from '@angular/http';

describe('ProductService', () => {
  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        ProductService,
        MockBackend,
        BaseRequestOptions,
        // 关键:配置模拟的Http服务,让MockBackend能拦截请求
        {
          provide: Http,
          useFactory: (backend: MockBackend, options: BaseRequestOptions) => 
              new Http(backend, options),
          deps: [MockBackend, BaseRequestOptions]
        }
      ]
    });
  });

  it('should return correct product list', fakeAsync(inject([ProductService, MockBackend], 
    (service: ProductService, mockBackend: MockBackend) => {
      // 准备模拟数据,要匹配Service里的解析逻辑(Service取的是StatusDescription)
      const mockResponseData = {
        StatusDescription: [
          { id: 0, details: 'All cats are lions' },
          { id: 1, details: 'Video 1' },
          { id: 2, details: 'Video 2' },
          { id: 3, details: 'Video 3' }
        ]
      };

      // 拦截所有Http连接,返回模拟响应
      mockBackend.connections.subscribe((connection: MockConnection) => {
        connection.mockRespond(new Response(
          new ResponseOptions({ body: mockResponseData })
        ));
      });

      let fetchedProducts: any[];
      // 调用Service方法
      service.productsgetall().subscribe(products => {
        fetchedProducts = products;
      });

      // 刷新微任务队列,触发所有异步回调(包括MockBackend和subscribe的回调)
      flushMicrotasks();

      // 做断言
      expect(fetchedProducts.length).toBe(4);
      expect(fetchedProducts[0].details).toBe('All cats are lions');
    })
  ));
});

方式2:用async + inject(不用手动管异步触发)

这种方式不需要fakeAsync和flushMicrotasks,async会自动处理异步流程:

import { TestBed, async, inject } from '@angular/core/testing';
import { ProductService } from './product.service';
import { MockBackend, MockConnection } from '@angular/http/testing';
import { Http, BaseRequestOptions, Response, ResponseOptions } from '@angular/http';

describe('ProductService', () => {
  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        ProductService,
        MockBackend,
        BaseRequestOptions,
        {
          provide: Http,
          useFactory: (backend: MockBackend, options: BaseRequestOptions) => 
              new Http(backend, options),
          deps: [MockBackend, BaseRequestOptions]
        }
      ]
    });
  });

  it('should return correct product list', async(inject([ProductService, MockBackend], 
    (service: ProductService, mockBackend: MockBackend) => {
      const mockResponseData = {
        StatusDescription: [
          { id: 0, details: 'All cats are lions' },
          { id: 1, details: 'Video 1' },
          { id: 2, details: 'Video 2' },
          { id: 3, details: 'Video 3' }
        ]
      };

      mockBackend.connections.subscribe((connection: MockConnection) => {
        connection.mockRespond(new Response(
          new ResponseOptions({ body: mockResponseData })
        ));
      });

      // 直接返回Observable,async会等待它完成
      return service.productsgetall().subscribe(products => {
        expect(products.length).toBe(4);
        expect(products[0].details).toBe('All cats are lions');
      });
    })
  ));
});

几个关键细节要注意

  1. 必须配置模拟Http:一定要在providers里用MockBackend和BaseRequestOptions创建Http实例,不然ProductService用的还是真实的Http,肯定会发真实请求。
  2. 模拟响应要匹配Service逻辑:你的ProductService里是从response.json().StatusDescription取数据,所以模拟的响应body必须包含这个字段——之前你直接返回数组,会导致res.StatusDescription是undefined,映射的时候肯定出问题。
  3. 异步测试用法别乱混:fakeAsync配flushMicrotasks,async不用done,选一种方式就行,别混搭。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 08:22:45