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

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

问题原因

  1. fixture.detectChanges()提前触发生命周期:在beforeEach中调用该方法时,组件已完成初始化,ngOnInit已执行,后续手动调用component.ngOnInit()时,Spy尚未创建。
  2. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 18:47:28