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

多实例服务引发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还没创建,也会导致捕获不到调用。

修复步骤

  1. 直接获取组件内部的服务实例(测试场景下可通过私有属性访问,或改为protected属性)
  2. 在组件的服务实例上打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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 10:07:46