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被触发 })); });
关键知识点说明
- 模拟依赖服务:用
useValue提供模拟的AuthService,避免真实服务的状态影响测试结果;用Jasmine Spy可以跟踪方法是否被调用、调用次数等。 - HttpTestingController:用来拦截和模拟HTTP请求,你可以验证请求的URL、方法、头信息,还能自定义返回的响应内容,完全脱离真实后端环境。
- fakeAsync & tick:处理Observable的异步逻辑,让异步回调同步执行,方便在测试中及时断言结果。
- 分支覆盖:不仅测试正常成功的场景,还要测试错误分支(比如StatusCode=1触发logout),保证服务逻辑的完整性。
内容的提问来源于stack exchange,提问作者user9699663
相关产品推荐
相关产品推荐

