Angular 18 + Jest:无法监视Signal的set方法
问题:Angular组件初始化时修改Signal值的Jest测试失败
环境
- Angular 18.2.8
- Jest 29.7.0
需求
验证组件在ngOnInit方法中将服务的loading Signal设置为true。
service.ts
import { signal, Injectable } from '@angular/core'; @Injectable() export class CardStateService { loading = signal<boolean>(false); }
component.ts
import { Component, OnInit, inject, ChangeDetectionStrategy } from '@angular/core'; import { CommonModule } from '@angular/common'; import { UntilDestroy } from '@ngneat/until-destroy'; import { CardStateService } from './card-state.service'; @UntilDestroy() @Component({ selector: 'component-card', templateUrl: './component.html', styleUrls: ['./component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [CommonModule], providers: [CardStateService], }) export class Component implements OnInit { stateService = inject(CardStateService); ngOnInit(): void { this.stateService.loading.set(true); } }
测试代码(component.spec.ts)
import { signal } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { MockProvider } from 'ng-mocks'; import { CardStateService } from '../../shared/card-state.service'; import { Component } from './component'; describe('Component', () => { let component: Component; let fixture: ComponentFixture<Component>; let cardStateServiceMock: CardStateService; let mockStateService = { loading: signal(false), }; beforeEach(() => { TestBed.configureTestingModule({ imports: [Component], providers: [ MockProvider(CardStateService, mockStateService), ], }); fixture = TestBed.createComponent(Component); component = fixture.componentInstance; cardStateServiceMock = TestBed.inject(CardStateService); fixture.detectChanges(); }); it('should set loading to true when initializing component', () => { const loadingSpy = jest.spyOn(mockStateService.loading, 'set') component.ngOnInit(); expect(loadingSpy).toHaveBeenCalledWith(true); }); });
测试失败结果
Component› should set loading to true when initializing component expect(jest.fn()).toHaveBeenCalledWith(...expected) Expected: true Number of calls: 0
问题原因
fixture.detectChanges()提前触发生命周期:在beforeEach中调用该方法时,组件已完成初始化,ngOnInit已执行,后续手动调用component.ngOnInit()时,Spy尚未创建。- Spy对象与组件实际使用的实例不一致:虽然使用
mockStateService创建Mock,但测试中Spy的对象和组件注入的服务实例可能存在引用差异,导致无法捕获调用。
修复方案
方案1:模拟set方法并控制生命周期触发时机
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { MockProvider } from 'ng-mocks'; import { CardStateService } from '../../shared/card-state.service'; import { Component } from './component'; describe('Component', () => { let component: Component; let fixture: ComponentFixture<Component>; let cardStateService: CardStateService; let loadingSetSpy: jest.SpyInstance; beforeEach(() => { TestBed.configureTestingModule({ imports: [Component], providers: [ MockProvider(CardStateService, { loading: { set: jest.fn() } // 直接将set方法模拟为jest.fn }), ], }); fixture = TestBed.createComponent(Component); component = fixture.componentInstance; cardStateService = TestBed.inject(CardStateService); loadingSetSpy = jest.spyOn(cardStateService.loading, 'set'); // 不在beforeEach中调用fixture.detectChanges() }); it('should set loading to true when initializing component', () => { fixture.detectChanges(); // 手动触发组件初始化 expect(loadingSetSpy).toHaveBeenCalledWith(true); }); });
方案2:保留真实Signal行为并验证
如果需要验证Signal的实际值变化,可使用此方案:
import { signal } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { MockProvider } from 'ng-mocks'; import { CardStateService } from '../../shared/card-state.service'; import { Component } from './component'; describe('Component', () => { let component: Component; let fixture: ComponentFixture<Component>; let mockLoadingSignal = signal(false); beforeEach(() => { TestBed.configureTestingModule({ imports: [Component], providers: [ MockProvider(CardStateService, { loading: mockLoadingSignal }), ], }); fixture = TestBed.createComponent(Component); component = fixture.componentInstance; }); it('should set loading to true when initializing component', () => { const setSpy = jest.spyOn(mockLoadingSignal, 'set'); fixture.detectChanges(); // 触发初始化 expect(setSpy).toHaveBeenCalledWith(true); expect(mockLoadingSignal()).toBe(true); // 验证Signal值变化 }); });
关键修复点
- 避免在
beforeEach中提前调用fixture.detectChanges(),将生命周期触发时机留给测试用例控制。 - 确保Spy创建在组件实际注入的服务实例上,保证引用一致性。
内容的提问来源于stack exchange,提问作者Alefe Jamarino
相关产品推荐
相关产品推荐

