Angular服务单元测试问题:构造函数调用HTTP后globalConfig未定义
解决Angular单元测试中GlobalConfigService的globalConfig未定义问题
我是Angular单元测试新手,想要为构造函数中调用HTTP请求的GlobalConfigService编写单元测试,但发现globalConfig属性始终为undefined。
服务代码如下:
@Injectable({ providedIn: "root", }) export class GlobalConfigService { globalConfig: GlobalConfig= new GlobalConfig(); constructor(private http: HttpClient, private configService: ConfigService) { if (configService.getData() == undefined){ let data:any; http.get('./assets/config/config.json').subscribe( ok => { data = ok; http.get<any>(data.baseUrl + 'config/app').subscribe( response => { response.baseUrl = data.baseUrl; configService.setData(response); this.globalConfig = <GlobalConfig>{ baseUrl: configService.getData()["baseUrl"], language: configService.getData()["language"], }; }, error => { console.log(error); } ) }, error => { console.log(error); } ) } else{ this.globalConfig = <GlobalConfig>{ baseUrl: configService.getData()["baseUrl"], language: configService.getData()["language"], }; } } get Config(): Promise<GlobalConfig> { return new Promise((resolve, reject) => { resolve(this.globalConfig); }); } get appConfig(): GlobalConfig { return this.globalConfig; } }
我编写的单元测试代码如下:
describe('GlobalConfigService', () => { let httpTestingController: HttpTestingController; let ConfigServiceSpy: ConfigService; let mockGlobalConfig: GlobalConfigService; beforeEach(() => { ConfigServiceSpy = jasmine.createSpyObj('ConfigService', ['getData','setData']); TestBed.configureTestingModule({ imports: [HttpClientTestingModule], providers: [GlobalConfigService, {provider: ConfigService, useValue: ConfigServiceSpy}] }); httpClient = TestBed.inject(HttpClient); httpTestingController = TestBed.inject(HttpTestingController); mockGlobalConfig = TestBed.inject(GlobalConfigService); ConfigServiceSpy = TestBed.inject(ConfigService); }); it('should return config ', () => { let response = { baseUrl: "http://localhost:8080/", language: "fa", }; const req = httpTestingController.expectOne( "./assets/config/config.json" ); expect(req.request.method).toEqual("GET"); req.flush(response); expect(mockGlobalConfig.appConfig.baseUrl).toBe(response.baseUrl); }); });
运行测试时报错:Expected undefined to be 'http://localhost:8080/',请问该如何解决?
问题根源
- 未处理嵌套HTTP请求:服务构造函数发起了两层嵌套的HTTP请求,测试仅处理了第一个
config.json请求,第二个data.baseUrl + 'config/app'请求未被响应,导致globalConfig赋值逻辑未执行。 - 异步操作未等待:HTTP订阅是异步执行的,测试中直接同步断言
appConfig.baseUrl时,订阅逻辑还未完成,globalConfig仍处于初始状态。 - Spy返回值未显式配置:未明确设置
ConfigServiceSpy.getData()返回undefined,虽然默认返回undefined,但显式配置能确保触发构造函数中的HTTP请求分支。
修复后的测试代码
describe('GlobalConfigService', () => { let httpTestingController: HttpTestingController; let ConfigServiceSpy: jasmine.SpyObj<ConfigService>; let mockGlobalConfig: GlobalConfigService; beforeEach(() => { ConfigServiceSpy = jasmine.createSpyObj('ConfigService', ['getData','setData']); TestBed.configureTestingModule({ imports: [HttpClientTestingModule], providers: [GlobalConfigService, {provide: ConfigService, useValue: ConfigServiceSpy}] }); httpTestingController = TestBed.inject(HttpTestingController); mockGlobalConfig = TestBed.inject(GlobalConfigService); }); it('should initialize globalConfig correctly', fakeAsync(() => { // 1. 配置Spy返回undefined,触发HTTP请求逻辑 ConfigServiceSpy.getData.and.returnValue(undefined); // 2. 定义两个请求的响应数据 const configJsonResponse = { baseUrl: "http://localhost:8080/" }; const appConfigResponse = { language: "fa" }; // 3. 处理第一个请求:config.json const configReq = httpTestingController.expectOne("./assets/config/config.json"); expect(configReq.request.method).toBe("GET"); configReq.flush(configJsonResponse); // 4. 处理第二个嵌套请求:baseUrl + config/app const appConfigReq = httpTestingController.expectOne(`${configJsonResponse.baseUrl}config/app`); expect(appConfigReq.request.method).toBe("GET"); appConfigReq.flush(appConfigResponse); // 5. 等待所有异步订阅完成 tick(); // 6. 验证setData被正确调用 expect(ConfigServiceSpy.setData).toHaveBeenCalledWith({ ...appConfigResponse, baseUrl: configJsonResponse.baseUrl }); // 7. 断言globalConfig的值 expect(mockGlobalConfig.appConfig.baseUrl).toBe(configJsonResponse.baseUrl); expect(mockGlobalConfig.appConfig.language).toBe(appConfigResponse.language); // 验证所有HTTP请求都已处理 httpTestingController.verify(); })); });
核心修复点
- 显式配置Spy返回值:通过
ConfigServiceSpy.getData.and.returnValue(undefined)确保触发构造函数中的HTTP请求分支。 - 处理所有HTTP请求:捕获并响应第二层嵌套的
config/app请求,保证后续赋值逻辑执行。 - 使用
fakeAsync+tick():等待所有异步订阅完成后再进行断言,避免异步时序问题。 - 验证请求完整性:添加
httpTestingController.verify()确保没有未处理的HTTP请求。
内容的提问来源于stack exchange,提问作者ali
相关产品推荐
相关产品推荐

