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

Angular 17+ 含Signals与Effects的Service测试问题解决

测试Angular Signal Effect触发的服务调用链问题

我有一个ServiceA,通过公开方法更新内部私有Signal,Signal值变化时会触发构造函数中定义的Effect,进而调用私有方法,最终调用ServiceB的公开方法。功能运行正常,但测试时无法验证整个调用链,测试总是失败,提示ServiceB的方法未被调用。

示例代码

ServiceA实现

@Injectable({
  providedIn: 'root'
})
export class ServiceA {
  private signalA = signal(0);

  constructor(private readonly serviceB: ServiceB) {
    effect(() => {
      const signalAValue = this.signalA();
      this.privateMethodA(signalAValue);
    });
  }

  public publicMethodA(value: number): void {
    this.signalA.set(value);
  }

  private privateMethodA(arg: number): void {
    this.serviceB.publicMethodB(arg);
  }
}

测试代码

describe('ServiceA', () => {
  let serviceA: ServiceA;
  let serviceB: ServiceB;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        ServiceA,
        ServiceB
      ]
    });

    serviceA = TestBed.inject(ServiceA);
    serviceB = TestBed.inject(ServiceB);
  });

  it('should call publicMethodB of ServiceB when publicMethodA of ServiceA is called', fakeAsync(() => {
    jest.spyOn(serviceB, 'publicMethodB');
    serviceA.publicMethodA(1);

    expect(serviceB.publicMethodB).toHaveBeenCalled();
  }));
});

测试失败信息

Expected number of calls: >= 1
Received number of calls: 0

问题原因

Angular的effect是异步执行的,当Signal更新后,Effect的回调不会立即同步执行,而是被加入到微任务队列中。在fakeAsync测试环境下,如果不手动触发微任务执行,断言会在Effect执行前就运行,导致检测不到ServiceB方法的调用。

解决方案

在fakeAsync测试中,调用tick()或flush()方法,手动推进时间并执行所有待处理的微任务,确保Effect完成执行后再进行断言。

修改后的测试代码

describe('ServiceA', () => {
  let serviceA: ServiceA;
  let serviceB: ServiceB;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        ServiceA,
        ServiceB
      ]
    });

    serviceA = TestBed.inject(ServiceA);
    serviceB = TestBed.inject(ServiceB);
  });

  it('should call publicMethodB of ServiceB when publicMethodA of ServiceA is called', fakeAsync(() => {
    const spy = jest.spyOn(serviceB, 'publicMethodB');
    serviceA.publicMethodA(1);
    
    // 触发所有待执行的微任务,包括Effect的回调
    tick();

    expect(spy).toHaveBeenCalledWith(1);
  }));
});

补充说明

  • tick()会模拟时间流逝,执行所有已排队的微任务,适合这种场景
  • flush()会执行所有微任务和宏任务,效果更强,如果有其他异步操作也能处理
  • 也可以用waitForAsync配合async/await,但fakeAsync + tick在服务测试中更简洁直观

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 02:22:48