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

Angular Jasmine单元测试:Spy服务未被调用问题求助

Angular Jasmine单元测试:组件级Providers覆盖TestBed模拟服务问题

组件代码

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
  providers: [HomeService]
})
export class AppComponent {
  title = 'testing-angular';

  constructor (private service: HomeService ) {}

  CallHomeService(): void {
    this.service.DoStuff();
  }
}

服务代码

@Injectable()
export class HomeService {

  constructor() { }

  DoStuff(): void {
    console.log('homeService has been called')
  }

}

初始测试代码(执行失败)

describe('AppComponent', () => {
  let fixture: ComponentFixture<AppComponent>;
  let appComponent: AppComponent;
  let homeServiceMock: jasmine.SpyObj<HomeService>;

  beforeEach(() => {
    homeServiceMock = jasmine.createSpyObj('HomeService', ['DoStuff']);

    TestBed.configureTestingModule({
      declarations: [AppComponent],
      providers: [{provide: HomeService, useValue: homeServiceMock}]
    });

    fixture = TestBed.createComponent(AppComponent);
    appComponent = fixture.componentInstance;
  });

  it('should call DoStuff() when CallHomeService() is called', () => {
    appComponent.CallHomeService();
    expect(homeServiceMock.DoStuff).toHaveBeenCalled();
  });
});

报错信息

AppComponent should call DoStuff() when CallHomeService() is called FAILED
        Expected spy HomeService.DoStuff to have been called.
            at <Jasmine>
            at UserContext.apply (src/app/app.component.spec.ts:28:37)
            at _ZoneDelegate.invoke (node_modules/zone.js/fesm2015/zone.js:375:26)
            at ProxyZoneSpec.onInvoke (node_modules/zone.js/fesm2015/zone-testing.js:287:39)
            at _ZoneDelegate.invoke (node_modules/zone.js/fesm2015/zone.js:374:52)

修改后成功的测试代码

import { TestBed, ComponentFixture } from '@angular/core/testing';
import { AppComponent } from './app.component';
import { HomeService } from './service/home.service';

describe('AppComponent', () => {
  let fixture: ComponentFixture<AppComponent>;
  let appComponent: AppComponent;
  let homeService: jasmine.SpyObj<HomeService>;

  beforeEach(() => {
    const homeServiceMock = jasmine.createSpyObj('HomeService', ['DoStuff']);

    TestBed.configureTestingModule({
      declarations: [AppComponent],
      providers: []
    });

    TestBed.overrideProvider(HomeService, { useValue: homeServiceMock });

    fixture = TestBed.createComponent(AppComponent);
    appComponent = fixture.componentInstance;
  });

  it('should call DoStuff() when CallHomeService() is called', () => {
    appComponent.CallHomeService();
    expect(homeService.DoStuff).toHaveBeenCalled();
  });
});

问题原因与解释

Angular依赖注入系统存在明确的优先级层级:组件自身providers数组声明的服务,优先级高于模块级(包括TestBed配置的测试模块)的providers。

当在TestBed.configureTestingModule的providers中提供模拟服务时,该模拟仅注册在测试模块层级。但AppComponent的@Component装饰器中已声明providers: [HomeService],这会在组件的注入器中创建全新的HomeService实例,完全覆盖测试模块提供的模拟对象——测试中调用CallHomeService()时,实际执行的是组件自身创建的真实服务实例,而非定义的spy对象,因此断言失败。

TestBed.overrideProvider()方法的核心作用就是覆盖组件级别的providers配置,它会直接修改组件元数据中的providers,替换为指定的模拟服务,确保组件注入器使用mock实例,测试断言即可正常生效。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 21:37:35