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

Angular中PassengerDetailsService的handleError单元测试覆盖率优化求助

提升PassengerDetailsService中handleError方法的代码覆盖率

问题背景

需要为passenger-detail.service.specs.ts中的handleError方法提升代码覆盖率,当前收到「function not covered」和「statement not covered」的未覆盖提示,服务实现代码如下:

import { Injectable } from "@angular/core";
import { Observable, catchError, throwError } from "rxjs";
import {
  HttpClient,
  HttpErrorResponse,
  HttpHeaders,
} from "@angular/common/http";
import { AuthCookiesService } from "@britishairways-ent/cookie";
import { environment } from "src/environments/environment";
import { PassengerApis } from "src/app/interfaces/apis-passenger.interface";

@Injectable({
  providedIn: "root",
})
export class PassengerDetailsService {
  constructor(
    private http: HttpClient,
    private authCookiesService: AuthCookiesService
  ) {}

  addApisData(passengerDetails: PassengerApis): Observable<any> {
    const url = environment.SAVE_APIS;
    return this.http
      .post(url, passengerDetails, {
        observe: "response",
        headers: new HttpHeaders({
          "Content-Type": "application/json",
          Authorization:
            "Bearer " + this.authCookiesService.getPublicAccessToken(),
        }),
      })
      .pipe(catchError(this.handleError));
  }

  handleError(error: HttpErrorResponse) {
    if (
      error.error &&
      typeof error.error.code !== "undefined" &&
      typeof error.error.description !== "undefined"
    ) {
      return throwError(() => error.error.code);
    }
  }
}

解决方案

1. 先完善handleError方法

原方法在不符合if条件时无返回值,会导致运行时错误,同时也无法覆盖该分支,先补充默认返回逻辑:

handleError(error: HttpErrorResponse) {
  if (
    error.error &&
    typeof error.error.code !== "undefined" &&
    typeof error.error.description !== "undefined"
  ) {
    return throwError(() => error.error.code);
  }
  // 处理不符合条件的错误场景,返回默认错误信息
  return throwError(() => error.message || 'Unknown error');
}

2. 编写测试用例覆盖所有分支

在passenger-detail.service.specs.ts中添加以下测试代码,覆盖handleError的所有执行路径:

import { TestBed } from '@angular/core/testing';
import { PassengerDetailsService } from './passenger-details.service';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { AuthCookiesService } from '@britishairways-ent/cookie';
import { throwError } from 'rxjs';

describe('PassengerDetailsService', () => {
  let service: PassengerDetailsService;
  let httpClientSpy: jasmine.SpyObj<HttpClient>;
  let authCookiesSpy: jasmine.SpyObj<AuthCookiesService>;

  beforeEach(() => {
    // 创建依赖的模拟对象
    const httpSpy = jasmine.createSpyObj('HttpClient', ['post']);
    const authCookieSpy = jasmine.createSpyObj('AuthCookiesService', ['getPublicAccessToken']);

    TestBed.configureTestingModule({
      providers: [
        PassengerDetailsService,
        { provide: HttpClient, useValue: httpSpy },
        { provide: AuthCookiesService, useValue: authCookieSpy }
      ]
    });

    service = TestBed.inject(PassengerDetailsService);
    httpClientSpy = TestBed.inject(HttpClient) as jasmine.SpyObj<HttpClient>;
    authCookiesSpy = TestBed.inject(AuthCookiesService) as jasmine.SpyObj<AuthCookiesService>;
    // 模拟token返回值
    authCookiesSpy.getPublicAccessToken.and.returnValue('mock-token');
  });

  describe('handleError', () => {
    // 测试符合条件的错误场景
    it('返回错误码当错误对象包含code和description', (done: DoneFn) => {
      const testError = new HttpErrorResponse({
        error: { code: 'API_ERR_001', description: '无效乘客信息' },
        status: 400,
        statusText: 'Bad Request'
      });

      service.handleError(testError).subscribe({
        error: (err) => {
          expect(err).toBe('API_ERR_001');
          done();
        }
      });
    });

    // 测试所有不符合条件的错误分支
    it('返回默认错误信息当错误对象缺少code或description', (done: DoneFn) => {
      // 场景1:error.error不存在
      const errorWithoutErrorObj = new HttpErrorResponse({
        status: 500,
        statusText: 'Internal Server Error'
      });
      service.handleError(errorWithoutErrorObj).subscribe({
        error: (err) => {
          expect(err).toBe('Internal Server Error');
        }
      });

      // 场景2:error.error存在但无code
      const errorWithoutCode = new HttpErrorResponse({
        error: { description: '参数错误' },
        status: 400,
        statusText: 'Bad Request'
      });
      service.handleError(errorWithoutCode).subscribe({
        error: (err) => {
          expect(err).toBe('Bad Request');
        }
      });

      // 场景3:error.error存在但无description
      const errorWithoutDesc = new HttpErrorResponse({
        error: { code: 'API_ERR_002' },
        status: 400,
        statusText: 'Bad Request'
      });
      service.handleError(errorWithoutDesc).subscribe({
        error: (err) => {
          expect(err).toBe('Bad Request');
          done();
        }
      });
    });

    // 测试通过addApisData触发handleError的场景
    it('当addApisData请求失败时触发handleError', (done: DoneFn) => {
      const testError = new HttpErrorResponse({
        error: { code: 'API_ERR_003', description: '保存失败' },
        status: 400,
        statusText: 'Bad Request'
      });
      // 模拟post请求抛出错误
      httpClientSpy.post.and.returnValue(throwError(() => testError));

      service.addApisData({} as any).subscribe({
        error: (err) => {
          expect(err).toBe('API_ERR_003');
          expect(httpClientSpy.post).toHaveBeenCalled();
          done();
        }
      });
    });
  });
});

3. 覆盖率说明

  • 第一个测试覆盖handleError中if条件为真的分支。
  • 第二个测试覆盖if条件为假的所有子分支(无error对象、缺code、缺description)。
  • 第三个测试覆盖handleError通过addApisData被调用的路径,确保方法的调用链路也被覆盖。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 02:08:11