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

如何测试依赖Http的Angular CityService?解决注入错误

解决Angular CityService测试的依赖注入问题

你遇到的NullInjectorError: No provider for Http!错误,本质是测试模块没有为CityService的依赖项(Http、Router、AuthService)提供对应的服务实例。在Angular单元测试中,我们通常不会使用真实的HTTP服务或路由服务,而是通过模拟依赖或者使用Angular提供的测试模块来隔离测试目标。

1. 准备模拟依赖

首先,我们需要为AuthService和Router创建模拟类——我们不需要测试这些依赖的功能,只需要模拟它们的核心行为来支撑CityService的测试:

// 模拟AuthService,实现原服务的核心方法
class MockAuthService {
  getCurrentUser() {
    return { token: 'test-demo-token' }; // 返回测试用的token
  }
  logout() {} // 空方法,后续用间谍函数监控调用情况
}

// 模拟Router,原CityService仅注入未使用,所以留空即可
class MockRouter {}

2. 配置测试模块

在beforeEach中,我们需要导入Angular官方的HttpClientTestingModule(用于模拟HTTP请求),并将模拟的依赖和CityService注册到测试模块中:

import { TestBed } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { CityService } from './city.service';
import { City } from './city.model'; // 导入你的City模型类
import { AuthService } from './auth.service';
import { Router } from '@angular/router';
import { Api } from './api.config'; // 导入你的Api配置类

describe('Service: City', () => {
  let cityService: CityService;
  let httpTestingController: HttpTestingController;
  let authService: MockAuthService;

  beforeEach(() => {
    authService = new MockAuthService();

    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule], // 导入HTTP测试模块,模拟Http服务
      providers: [
        CityService,
        { provide: AuthService, useValue: authService }, // 替换为模拟的AuthService
        { provide: Router, useClass: MockRouter } // 替换为模拟的Router
      ]
    });

    // 获取服务实例
    cityService = TestBed.inject(CityService);
    httpTestingController = TestBed.inject(HttpTestingController);
  });

  // 测试完成后验证所有HTTP请求都已处理,避免遗漏
  afterEach(() => {
    httpTestingController.verify();
  });

3. 编写测试用例

现在可以针对getAllCity的两种核心场景编写测试:

测试成功返回城市列表

it('#getAllCity should return an Observable of City[] with correct data', () => {
  const mockCityData = [
    { id: 1, name: 'Beijing' },
    { id: 2, name: 'Shanghai' }
  ];

  cityService.getAllCity().subscribe(cities => {
    expect(cities.length).toBe(2);
    expect(cities[0].name).toBe('Beijing');
    expect(cities.every(city => city instanceof City)).toBe(true);
  });

  // 捕获目标HTTP请求
  const req = httpTestingController.expectOne(Api.getUrl(Api.URLS.getAllCity));
  // 验证请求方法和请求头
  expect(req.request.method).toBe('GET');
  expect(req.request.headers.get('x-access-token')).toBe('test-demo-token');

  // 返回模拟的成功响应
  req.flush({
    StatusCode: 0, // 假设0代表请求成功
    StatusDescription: mockCityData
  });
});

测试StatusCode为1时触发登出

it('#getAllCity should call auth.logout when StatusCode is 1', () => {
  // 为logout方法创建间谍,监控是否被调用
  spyOn(authService, 'logout');

  cityService.getAllCity().subscribe({
    complete: () => {
      expect(authService.logout).toHaveBeenCalled();
    }
  });

  const req = httpTestingController.expectOne(Api.getUrl(Api.URLS.getAllCity));
  // 返回模拟的异常响应
  req.flush({
    StatusCode: 1,
    StatusDescription: []
  });
});

4. 完整测试代码示例

把以上部分整合起来,就是完整可运行的测试代码:

import { TestBed } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { CityService } from './city.service';
import { City } from './city.model';
import { AuthService } from './auth.service';
import { Router } from '@angular/router';
import { Api } from './api.config';

// 模拟AuthService
class MockAuthService {
  getCurrentUser() {
    return { token: 'test-demo-token' };
  }
  logout() {}
}

// 模拟Router
class MockRouter {}

describe('Service: City', () => {
  let cityService: CityService;
  let httpTestingController: HttpTestingController;
  let authService: MockAuthService;

  beforeEach(() => {
    authService = new MockAuthService();

    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [
        CityService,
        { provide: AuthService, useValue: authService },
        { provide: Router, useClass: MockRouter }
      ]
    });

    cityService = TestBed.inject(CityService);
    httpTestingController = TestBed.inject(HttpTestingController);
  });

  afterEach(() => {
    httpTestingController.verify();
  });

  it('#getAllCity should return an Observable of City[] with correct data', () => {
    const mockCityData = [
      { id: 1, name: 'Beijing' },
      { id: 2, name: 'Shanghai' }
    ];

    cityService.getAllCity().subscribe(cities => {
      expect(cities.length).toBe(2);
      expect(cities[0].id).toBe(1);
      expect(cities[1].name).toBe('Shanghai');
      expect(cities.every(city => city instanceof City)).toBe(true);
    });

    const req = httpTestingController.expectOne(Api.getUrl(Api.URLS.getAllCity));
    expect(req.request.method).toBe('GET');
    expect(req.request.headers.get('x-access-token')).toBe('test-demo-token');

    req.flush({
      StatusCode: 0,
      StatusDescription: mockCityData
    });
  });

  it('#getAllCity should trigger auth.logout when StatusCode is 1', () => {
    spyOn(authService, 'logout');

    cityService.getAllCity().subscribe({
      complete: () => {
        expect(authService.logout).toHaveBeenCalled();
      }
    });

    const req = httpTestingController.expectOne(Api.getUrl(Api.URLS.getAllCity));
    req.flush({
      StatusCode: 1,
      StatusDescription: []
    });
  });
});

关键要点总结

  • 对于HTTP服务,使用HttpClientTestingModule和HttpTestingController模拟请求与响应,避免真实网络请求影响测试稳定性。
  • 对于其他依赖服务,通过模拟类或间谍函数隔离测试目标,只关注CityService自身的业务逻辑。
  • 测试完成后用httpTestingController.verify()确保所有HTTP请求都被处理,避免遗漏未捕获的请求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 09:24:02