如何为注入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参数的分支,可以单独构造用例,传入自定义的MockViewContainerRef,验证globalViewContainerRef不会被调用即可。 - 所有Mock的结构只需要覆盖你业务代码实际用到的属性和方法即可,不需要完整模拟整个类的所有API。
内容的提问来源于stack exchange,提问作者Sarcasm
相关产品推荐
相关产品推荐

