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

Angular中CityService测试报错:Cannot read property 'token' of null

解决Angular CityService测试中的"Cannot read property 'token' of null"错误

首先咱们来拆解下你遇到的问题:调用getAllCity()时,this.auth.getCurrentUser()返回了null,导致访问null.token抛出了错误。这是因为测试环境里没有初始化AuthService的用户状态,而且测试CityService的核心是隔离依赖——咱们应该用模拟的AuthService替代真实实现,这样才能专注测试CityService本身的逻辑,不受其他服务状态影响。

下面是完整的修正方案,我会一步步解释关键要点:

核心思路:模拟依赖+隔离测试

测试服务时,不要依赖真实的外部服务(比如AuthService),而是创建模拟对象来模拟它的行为;同时用Angular提供的HttpTestingController模拟HTTP请求,避免调用真实后端接口。

完整测试代码

import { TestBed, fakeAsync, tick } from '@angular/core/testing';
import { Http, ConnectionBackend } from '@angular/http';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { CityService } from './city.service';
import { AuthService } from './auth.service';
import { City } from './city.model';
import { Api } from './api.config'; // 假设你有对应的Api配置文件

// 模拟AuthService:提供测试需要的方法和返回值
const mockAuthService = {
  getCurrentUser: () => ({ token: 'fake-test-token' }), // 返回带token的模拟用户
  logout: jasmine.createSpy('logout') // 用Jasmine Spy跟踪方法调用情况
};

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

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [RouterTestingModule, HttpModule, HttpClientTestingModule],
      providers: [
        CityService,
        Http,
        ConnectionBackend,
        // 用模拟对象替换真实的AuthService
        { provide: AuthService, useValue: mockAuthService }
      ]
    });

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

  // 每次测试后清理未处理的HTTP请求
  afterEach(() => {
    httpTestingController.verify();
  });

  // 基础测试:验证服务能正常创建
  it('should create the service successfully', () => {
    expect(cityService).toBeTruthy();
  });

  // 测试正常分支:API返回成功状态时,返回City数组
  it('#getAllCity should return Observable<City[]> when API succeeds', fakeAsync(() => {
    // 模拟后端返回的原始数据
    const mockRawCities = [
      { id: 1, name: 'Beijing' },
      { id: 2, name: 'Shanghai' }
    ];
    // 预期转换后的City对象数组
    const expectedCities = mockRawCities.map(city => new City(city));

    // 订阅getAllCity的返回结果,进行断言
    cityService.getAllCity().subscribe(cities => {
      expect(cities).toEqual(expectedCities);
      expect(authService.logout).not.toHaveBeenCalled(); // 验证logout未被触发
    });

    // 捕获发出的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('fake-test-token'); // 验证请求头带token

    // 发送模拟响应(StatusCode≠1,不会触发logout)
    req.flush({
      StatusCode: 0,
      StatusDescription: mockRawCities
    });

    tick(); // 触发Observable的异步回调,同步执行断言
  });

  // 测试异常分支:API返回StatusCode=1时,调用AuthService的logout
  it('#getAllCity should call AuthService.logout when API returns StatusCode 1', fakeAsync(() => {
    cityService.getAllCity().subscribe({
      complete: () => {} // 因为无数据返回,监听complete事件
    });

    const req = httpTestingController.expectOne(Api.getUrl(Api.URLS.getAllCity));
    // 模拟返回错误状态码
    req.flush({
      StatusCode: 1,
      StatusDescription: []
    });

    tick();
    expect(authService.logout).toHaveBeenCalled(); // 验证logout被触发
  }));
});

关键知识点说明

  1. 模拟依赖服务:用useValue提供模拟的AuthService,避免真实服务的状态影响测试结果;用Jasmine Spy可以跟踪方法是否被调用、调用次数等。
  2. HttpTestingController:用来拦截和模拟HTTP请求,你可以验证请求的URL、方法、头信息,还能自定义返回的响应内容,完全脱离真实后端环境。
  3. fakeAsync & tick:处理Observable的异步逻辑,让异步回调同步执行,方便在测试中及时断言结果。
  4. 分支覆盖:不仅测试正常成功的场景,还要测试错误分支(比如StatusCode=1触发logout),保证服务逻辑的完整性。

内容的提问来源于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:23:34