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

如何在Jasmine中测试服务map()处理的JSON响应并解决测试报错

问题分析

你遇到的Error: Cannot make XHRs from within a fake async test是因为**MockBackend和fakeAsync不能混用**——MockBackend本身是同步模拟HTTP请求的,而fakeAsync会拦截异步操作(包括XHR),导致冲突。另外你最初的测试代码没有订阅Observable,也没有验证任何业务逻辑,自然无法有效测试。

接下来给你两种可行的测试方案,优先推荐官方现在主推的HttpClientTestingModule方案,再给你修复MockBackend的写法。


方案一:使用官方推荐的HttpClientTestingModule(推荐)

从Angular 4.3开始,官方推出了HttpClientTestingModule来简化HTTP服务的测试,比MockBackend更直观易用。

步骤1:配置测试模块

首先在TestBed中导入HttpClientTestingModule,并mock掉AuthService(因为我们需要验证logout方法是否被调用):

import { TestBed, inject } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { AlarmsService } from './alarms.service';
import { AuthService } from './auth.service';
import { Alarm } from './alarm.model';
import { Api } from './api.constants';

describe('AlarmsService', () => {
  let service: AlarmsService;
  let httpMock: HttpTestingController;
  let mockAuthService: jasmine.SpyObj<AuthService>;

  beforeEach(() => {
    // 创建AuthService的spy对象,用于验证logout调用
    mockAuthService = jasmine.createSpyObj('AuthService', ['logout']);

    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [
        AlarmsService,
        { provide: AuthService, useValue: mockAuthService }
      ]
    });

    service = TestBed.inject(AlarmsService);
    httpMock = TestBed.inject(HttpTestingController);
  });

  // 每次测试后验证所有HTTP请求都被处理
  afterEach(() => {
    httpMock.verify();
  });

步骤2:测试正常返回数据的场景(StatusCode≠1)

it('should map StatusDescription to Alarm instances when StatusCode is not 1', () => {
    const mockResponse = {
      StatusCode: 0,
      StatusDescription: [
        { alarmnumber: 0, alarmdesc: 'All cats are lions' },
        { alarmnumber: 1, alarmdesc: 'Video 1' }
      ]
    };

    service.typegetall().subscribe(alarms => {
      expect(alarms.length).toBe(2);
      // 验证返回的是Alarm实例
      expect(alarms[0] instanceof Alarm).toBeTruthy();
      expect(alarms[0].alarmdesc).toEqual('All cats are lions');
      // 验证auth.logout没有被调用
      expect(mockAuthService.logout).not.toHaveBeenCalled();
    });

    // 捕获并响应HTTP请求
    const req = httpMock.expectOne(Api.getUrl(Api.URLS.typegetall));
    expect(req.request.method).toBe('GET');
    req.flush(mockResponse);
  });

步骤3:测试StatusCode=1的场景(调用auth.logout)

it('should call auth.logout when StatusCode is 1', () => {
    const mockResponse = {
      StatusCode: 1,
      StatusDescription: []
    };

    service.typegetall().subscribe(alarms => {
      // 此时分支没有return,所以返回undefined
      expect(alarms).toBeUndefined();
      // 验证auth.logout被调用
      expect(mockAuthService.logout).toHaveBeenCalled();
    });

    const req = httpMock.expectOne(Api.getUrl(Api.URLS.typegetall));
    req.flush(mockResponse);
  });
});

方案二:修复MockBackend的报错问题

如果你坚持使用MockBackend,需要移除fakeAsync和tick(),因为MockBackend的响应是同步的,不需要fakeAsync来处理异步:

import { TestBed, inject } from '@angular/core/testing';
import { AlarmsService } from './alarms.service';
import { AuthService } from './auth.service';
import { MockBackend, MockConnection } from '@angular/http/testing';
import { Http, BaseRequestOptions, Response, ResponseOptions } from '@angular/http';
import { Alarm } from './alarm.model';
import { Api } from './api.constants';

describe('AlarmsService', () => {
  beforeEach(() => {
    const mockAuthService = jasmine.createSpyObj('AuthService', ['logout']);

    TestBed.configureTestingModule({
      providers: [
        AlarmsService,
        MockBackend,
        BaseRequestOptions,
        { provide: AuthService, useValue: mockAuthService },
        {
          provide: Http,
          useFactory: (backend: MockBackend, options: BaseRequestOptions) => new Http(backend, options),
          deps: [MockBackend, BaseRequestOptions]
        }
      ]
    });
  });

  it('should return Alarm instances when StatusCode is not 1', inject(
    [AlarmsService, MockBackend, AuthService],
    (service: AlarmsService, mockBackend: MockBackend, authService: AuthService) => {
      const mockResponse = {
        StatusCode: 0,
        StatusDescription: [
          { alarmnumber: 0, alarmdesc: 'All cats are lions' },
          { alarmnumber: 1, alarmdesc: 'Video 1' }
        ]
      };

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

      service.typegetall().subscribe(alarms => {
        expect(alarms.length).toBe(2);
        expect(alarms[0] instanceof Alarm).toBeTruthy();
        expect(alarms[0].alarmdesc).toEqual('All cats are lions');
        expect(authService.logout).not.toHaveBeenCalled();
      });
    }
  ));

  it('should call auth.logout when StatusCode is 1', inject(
    [AlarmsService, MockBackend, AuthService],
    (service: AlarmsService, mockBackend: MockBackend, authService: AuthService) => {
      const mockResponse = {
        StatusCode: 1,
        StatusDescription: []
      };

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

      service.typegetall().subscribe(alarms => {
        expect(alarms).toBeUndefined();
        expect(authService.logout).toHaveBeenCalled();
      });
    }
  ));
});

关键注意点

  1. 最初测试无效的原因:你只是expect(alarmeservice.typegetall());,但typegetall()返回的是Observable,必须订阅它才能触发内部逻辑,否则代码根本不会执行到HTTP请求和map操作符里的逻辑。
  2. 报错的根本原因:fakeAsync会拦截所有异步操作(包括XHR),而MockBackend是模拟的XHR,两者冲突,所以移除fakeAsync和tick()即可解决。
  3. 优先使用HttpClientTestingModule:MockBackend是针对老的Http服务的,现在Angular推荐使用HttpClient,对应的测试工具就是HttpClientTestingModule,API更简洁,维护性更好。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 07:53:47