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

Angular中嵌套API调用的单元测试全覆盖实现方法咨询

问题描述

我正在尝试为Angular中的嵌套API调用编写单元测试,但不知道如何覆盖所有代码行。附上嵌套API调用的业务代码及已尝试的测试代码,当前测试可运行但无法贡献代码覆盖率,寻求能覆盖所有代码行的测试方案,支持Jest/Jasmine框架。

业务代码

this.openService
  .createDocument(PIReq, this.id)
  .subscribe({
    next: (res1) => {
      const applicationReq = AppUtil.getApplicationRequest(
        this.openService.personDetails,
        res1.id
      );
      this.openService
        .createPersonAppliation(applicationReq, this.id)
        .subscribe({
          next: (res2) => {
            const personAppReqInfo: PersonApplicationIds = {
              documentId: res1.id,
              personApplicationId: res2.id,
            };
            
            if (this.openDataStoreService.details.length > 0) {
              this.submitApplicationRequest();
              this.invokeRequest();
            } else {
              this.invokeRequest();
            }
          },
          error: () => {
            this.showSpinner = false;
          },
        });
    },
    error: () => {
      this.showSpinner = false;
    },
 });

已尝试的测试代码

let sampleApplicationResponse!: ApplicationResponse;
jest.spyOn(openService, 'createPersonAppliation').mockReturnValueOnce(of(sampleApplicationResponse));
openService.createPersonAppliation(applicationReq, component.id)
  .subscribe({
    next: applicationRes => {
      expect(applicationRes).toBe(sampleApplicationResponse);
    }
  });

全覆盖测试方案

要覆盖所有代码路径,需要测试4种核心场景:双API调用成功(分details有数据/无数据)、createDocument调用失败、createPersonAppliation调用失败。以下是基于Jest的完整实现:

测试前置准备

先在测试文件中完成组件和依赖的初始化:

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { of, throwError } from 'rxjs';
import { YourComponent } from './your.component';
import { OpenService } from './open.service';
import { OpenDataStoreService } from './open-data-store.service';
import { AppUtil } from './app.util';

describe('YourComponent', () => {
  let component: YourComponent;
  let fixture: ComponentFixture<YourComponent>;
  let openService: jest.Mocked<OpenService>;
  let openDataStoreService: jest.Mocked<OpenDataStoreService>;

  beforeEach(async () => {
    // 创建依赖的Mock实例
    const openServiceMock = {
      createDocument: jest.fn(),
      createPersonAppliation: jest.fn(),
      personDetails: {} // 根据实际结构模拟
    } as jest.Mocked<OpenService>;
    
    const dataStoreMock = {
      details: []
    } as jest.Mocked<OpenDataStoreService>;

    await TestBed.configureTestingModule({
      declarations: [YourComponent],
      providers: [
        { provide: OpenService, useValue: openServiceMock },
        { provide: OpenDataStoreService, useValue: dataStoreMock }
      ]
    }).compileComponents();

    fixture = TestBed.createComponent(YourComponent);
    component = fixture.componentInstance;
    openService = TestBed.inject(OpenService) as jest.Mocked<OpenService>;
    openDataStoreService = TestBed.inject(OpenDataStoreService) as jest.Mocked<OpenDataStoreService>;
    
    // 静态工具方法Mock
    jest.spyOn(AppUtil, 'getApplicationRequest').mockReturnValue({ mock: 'request' });
    
    // 组件内部方法Mock,用于断言调用
    component.submitApplicationRequest = jest.fn();
    component.invokeRequest = jest.fn();
  });

场景1:双API成功 + details有数据

it('should call both submitApplicationRequest and invokeRequest when APIs succeed with non-empty details', () => {
  // 模拟API返回值
  const mockDocRes = { id: 'doc_001' };
  const mockAppRes = { id: 'app_001' };
  openService.createDocument.mockReturnValue(of(mockDocRes));
  openService.createPersonAppliation.mockReturnValue(of(mockAppRes));
  
  // 设置details有数据
  openDataStoreService.details = [{ id: 'detail_001' }];
  
  // 模拟组件参数并触发API调用(假设触发方法为submitAction)
  component.id = 'comp_001';
  const mockPIReq = { name: 'test' };
  component.submitAction(mockPIReq);

  // 断言API调用参数正确性
  expect(openService.createDocument).toHaveBeenCalledWith(mockPIReq, 'comp_001');
  expect(AppUtil.getApplicationRequest).toHaveBeenCalledWith(openService.personDetails, 'doc_001');
  expect(openService.createPersonAppliation).toHaveBeenCalledWith({ mock: 'request' }, 'comp_001');
  
  // 断言后续业务方法调用
  expect(component.submitApplicationRequest).toHaveBeenCalled();
  expect(component.invokeRequest).toHaveBeenCalled();
});

场景2:双API成功 + details无数据

it('should only call invokeRequest when APIs succeed with empty details', () => {
  const mockDocRes = { id: 'doc_001' };
  const mockAppRes = { id: 'app_001' };
  openService.createDocument.mockReturnValue(of(mockDocRes));
  openService.createPersonAppliation.mockReturnValue(of(mockAppRes));
  
  // 保持details为空数组
  openDataStoreService.details = [];
  
  component.id = 'comp_001';
  const mockPIReq = { name: 'test' };
  component.submitAction(mockPIReq);

  expect(component.submitApplicationRequest).not.toHaveBeenCalled();
  expect(component.invokeRequest).toHaveBeenCalled();
});

场景3:createDocument调用失败

it('should set showSpinner to false when createDocument fails', () => {
  component.showSpinner = true;
  openService.createDocument.mockReturnValue(throwError(() => new Error('API Error')));
  
  const mockPIReq = { name: 'test' };
  component.submitAction(mockPIReq);

  expect(component.showSpinner).toBe(false);
});

场景4:createPersonAppliation调用失败

it('should set showSpinner to false when createPersonAppliation fails', () => {
  component.showSpinner = true;
  const mockDocRes = { id: 'doc_001' };
  openService.createDocument.mockReturnValue(of(mockDocRes));
  openService.createPersonAppliation.mockReturnValue(throwError(() => new Error('API Error')));
  
  const mockPIReq = { name: 'test' };
  component.submitAction(mockPIReq);

  expect(component.showSpinner).toBe(false);
});
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 03:17:01