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

如何为注入ComponentFactoryResolver、ApplicationRef的Angular服务写Jasmine+Karma测试

报错根因

你运行测试时报错是因为测试环境没有初始化完整的Angular应用实例,原生ApplicationRef的components数组是空的,你调用this.applicationRef.components[0]自然拿到undefined,读取instance属性就会抛出对应错误。之前看到的「不需要Mock ApplicationRef」的说法并不适用于你的场景:你的服务明确依赖了ApplicationRef的属性做业务逻辑,必须手动提供符合调用结构的Mock值。


修改方案

步骤1:补充所有依赖的Mock逻辑

你需要同时Mock ApplicationRef、ViewContainerRef、ComponentFactory以及BannerComponent实例,覆盖showBanner方法里所有的调用路径。

步骤2:修改后的测试代码

import { ApplicationRef, ComponentFactoryResolver, ViewContainerRef } from "@angular/core";
import { fakeAsync, flush, TestBed } from "@angular/core/testing";
import { Subject } from "rxjs";
import { BannerComponent } from "src/app/components/banner/banner.component";
import { BannerSeverity } from "src/app/utils/utils";
import { getTypicalSetup } from "src/testing/typical-setup";
import { BannerService } from "./banner.service";

describe('BannerService', () => {
  const mock = getTypicalSetup();
  let bannerService: BannerService;
  let componentFactoryResolverSpy: jasmine.SpyObj<ComponentFactoryResolver>;
  let mockViewContainerRef: jasmine.SpyObj<ViewContainerRef>;
  let mockBannerComponentInstance: any;
  let mockApplicationRef: Partial<ApplicationRef>;

  beforeEach(fakeAsync(() => {
    // Mock BannerComponent实例:包含用到的属性和dismissed可观察对象
    mockBannerComponentInstance = {
      severity: null,
      message: null,
      dismissible: null,
      moreInfo: null,
      dismissed: new Subject<void>()
    };
    // Mock 组件实例的返回结构
    const mockComponentRef = {
      instance: mockBannerComponentInstance,
      destroy: jasmine.createSpy('destroy')
    };
    // Mock ViewContainerRef的createComponent方法
    mockViewContainerRef = jasmine.createSpyObj<ViewContainerRef>('ViewContainerRef', ['createComponent']);
    mockViewContainerRef.createComponent.and.returnValue(mockComponentRef);
    // Mock ApplicationRef:匹配你代码中用到的components结构
    mockApplicationRef = {
      components: [
        {
          instance: {
            viewContainerRef: mockViewContainerRef
          }
        }
      ]
    };
    // Mock ComponentFactoryResolver的resolveComponentFactory方法
    const mockComponentFactory = jasmine.createSpyObj('ComponentFactory', ['']);
    componentFactoryResolverSpy = jasmine.createSpyObj<ComponentFactoryResolver>('ComponentFactoryResolver', ['resolveComponentFactory']);
    componentFactoryResolverSpy.resolveComponentFactory.and.returnValue(mockComponentFactory);

    TestBed.configureTestingModule({
      imports: [...mock.imports],
      providers: [
        ...mock.providers,
        BannerService,
        { provide: ComponentFactoryResolver, useValue: componentFactoryResolverSpy },
        { provide: ApplicationRef, useValue: mockApplicationRef } // 新增ApplicationRef的Mock提供
      ],
    });
    bannerService = TestBed.inject(BannerService);
  }));

  it('should be created', () => {
    expect(bannerService).toBeDefined();
  });

  describe('#showBanner', () => {
    it('should create a banner with severity and message parameters passed', fakeAsync(() => {
      const severity: BannerSeverity = BannerSeverity.critical;
      const message: string = "Test Message";

      bannerService.showBanner(severity, message);
      flush();

      expect(bannerService.banners[0]).toBeDefined();
      // 可选补充断言:验证属性赋值正确
      expect(bannerService.banners[0].severity).toBe(BannerSeverity[severity]);
      expect(bannerService.banners[0].message).toBe(message);
      // 可选验证依赖方法被调用
      expect(componentFactoryResolverSpy.resolveComponentFactory).toHaveBeenCalledWith(BannerComponent);
      expect(mockViewContainerRef.createComponent).toHaveBeenCalled();
    }));
  });
});

补充说明

  • 如果你要测试传入appendTo参数的分支,可以单独构造用例,传入自定义的Mock ViewContainerRef,验证globalViewContainerRef不会被调用即可。
  • 所有Mock的结构只需要覆盖你业务代码实际用到的属性和方法即可,不需要完整模拟整个类的所有API。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.23 20:24:02