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'); }); }) )); });
几个关键细节要注意
- 必须配置模拟Http:一定要在
providers里用MockBackend和BaseRequestOptions创建Http实例,不然ProductService用的还是真实的Http,肯定会发真实请求。 - 模拟响应要匹配Service逻辑:你的
ProductService里是从response.json().StatusDescription取数据,所以模拟的响应body必须包含这个字段——之前你直接返回数组,会导致res.StatusDescription是undefined,映射的时候肯定出问题。 - 异步测试用法别乱混:
fakeAsync配flushMicrotasks,async不用done,选一种方式就行,别混搭。
内容的提问来源于stack exchange,提问作者site
相关产品推荐
相关产品推荐

