多实例服务引发Jasmine spyOn未被调用的组件测试问题求助
问题描述
组件代码
export class QrcodeComponent implements OnInit { constructor( private qrGenService: QrGeneratorService, ) { } private draw(): void { this.setOptions(); this.qrGenService.setInitOptions(this._options); this.qrGenService.generateQr(); } ngOnInit(): void { this.draw(); } }
测试代码
const qrServiceStub = { setInitOptions: () => {}, generateQr: () => {}, }; describe('QrCodeComponent', () => { let fixture: ComponentFixture<QrcodeComponent>; let component: QrcodeComponent; let qrService: QrGeneratorService; beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [ QrcodeComponent, ], providers: [ { provide: QrGeneratorService, useValue: qrServiceStub }, ], }) .compileComponents(); fixture = TestBed.createComponent(QrcodeComponent); component = fixture.componentInstance; qrService = TestBed.inject(QrGeneratorService); fixture.detectChanges(); })); it('should call setInitOptions with options passed & generateQr methods', () => { // Arrange const setInitOptionsSpy = spyOn(qrService, 'setInitOptions').and.callThrough(); const generateQrSpy = spyOn(qrService, 'generateQr').and.callThrough(); // Assert expect(setInitOptionsSpy).toHaveBeenCalledWith(component.options); expect(generateQrSpy).toHaveBeenCalled(); }); });
测试失败结果
- Error: Expected spy generateQr to have been called.
- Error: Expected spy setInitOptions to have been called with: [ Object ] but it was never called.
注:QrGeneratorService是多实例(multitone)服务。请问哪里操作有误?
问题原因与修复方案
核心问题
因为QrGeneratorService是多实例服务,每次注入都会生成新实例。你当前测试中,TestBed.inject(QrGeneratorService)拿到的实例,和组件构造函数里注入的不是同一个,所以给前者打的spy根本不会被组件调用,导致断言失败。另外,beforeEach里的fixture.detectChanges()会提前触发ngOnInit,此时spy还没创建,也会导致捕获不到调用。
修复步骤
- 直接获取组件内部的服务实例(测试场景下可通过私有属性访问,或改为
protected属性) - 在组件的服务实例上打spy,之后重新触发组件的初始化逻辑,确保spy能捕获调用
修改后的测试代码:
const qrServiceStub = { setInitOptions: () => {}, generateQr: () => {}, }; describe('QrCodeComponent', () => { let fixture: ComponentFixture<QrcodeComponent>; let component: QrcodeComponent; beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [QrcodeComponent], providers: [{ provide: QrGeneratorService, useValue: qrServiceStub }], }).compileComponents(); fixture = TestBed.createComponent(QrcodeComponent); component = fixture.componentInstance; // 不要在这里调用fixture.detectChanges(),避免提前触发ngOnInit })); it('should call setInitOptions with options passed & generateQr methods', () => { // Arrange:获取组件内部的服务实例 const componentQrService = component['qrGenService']; const setInitOptionsSpy = spyOn(componentQrService, 'setInitOptions').and.callThrough(); const generateQrSpy = spyOn(componentQrService, 'generateQr').and.callThrough(); // 触发组件初始化逻辑 component.ngOnInit(); // Assert expect(setInitOptionsSpy).toHaveBeenCalledWith(component.options); expect(generateQrSpy).toHaveBeenCalled(); }); });
额外说明
- 若不想访问私有属性,可把组件的
qrGenService改为protected,测试中直接用component.qrGenService访问 - 移除
beforeEach里的fixture.detectChanges(),避免提前触发ngOnInit导致spy未生效
内容的提问来源于stack exchange,提问作者Sasha Kalash
相关产品推荐
相关产品推荐

