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

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/',请问该如何解决?


问题根源

  1. 未处理嵌套HTTP请求:服务构造函数发起了两层嵌套的HTTP请求,测试仅处理了第一个config.json请求,第二个data.baseUrl + 'config/app'请求未被响应,导致globalConfig赋值逻辑未执行。
  2. 异步操作未等待:HTTP订阅是异步执行的,测试中直接同步断言appConfig.baseUrl时,订阅逻辑还未完成,globalConfig仍处于初始状态。
  3. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 18:23:24