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

Angular单元测试中如何Mock Signal触发Effect

Angular Signal单元测试:Mock Signal触发Effect的问题

我正在使用Angular Signal,通过effect函数执行信号变更操作。请问如何在单元测试中Mock Signal以触发effect?

相关实现代码

import { effect, Injector, Signal } from '@angular/core';

export const chainEffect = (initialEffectFn: () => void): EffectChainer => {
  return {
    configure: ({ delay, trigger, injector }) => ({
      run: (effectFn): void => {
        initialEffectFn();

        const destroyRef = effect(
          () => {
            if (delay()) {
              return;
            } else if (trigger()) {
              effectFn();
              destroyRef.destroy();
            } else {
              destroyRef.destroy();
              throw new Error('error');
            }
          },
          { injector },
        );
      },
    }),
  };
};

export type EffectChainer = {
  configure: (config: {
    delay: Signal<boolean>;
    trigger: Signal<boolean>;
    injector: Injector;
  }) => ChainedEffect;
};

export type ChainedEffect = {
  run: (effect: () => void) => void;
};

尝试的测试代码(未成功进入effect信号判断逻辑)

it('should run effect function if trigger signal is true', () => {
    let delay = signal<boolean>(false);
    let trigger = signal<boolean>(false);
     
    const chainer: EffectChainer = chainEffect(initialEffectFn);

    const chainedEffect: ChainedEffect = chainer.configure({ delay, trigger, injector });

    expect(()=>chainedEffect.run(effectFn)).toThrowError('error');
  
  });

解决方案

你的测试未按预期工作,核心原因有两点:

  1. Angular的effect默认是异步调度的,直接调用run后,effect的回调不会立即执行。
  2. 初始trigger信号值为false,即使effect执行也会进入else分支抛错,无法覆盖trigger为true的测试场景。

修正后的测试代码

使用Angular测试工具的fakeAsync和tick控制异步流程,同时正确更新Signal值触发effect:

import { fakeAsync, tick, TestBed } from '@angular/core/testing';
import { signal, Injector } from '@angular/core';

describe('chainEffect', () => {
  let injector: Injector;
  const initialEffectFn = jasmine.createSpy('initialEffectFn');
  const effectFn = jasmine.createSpy('effectFn');

  beforeEach(() => {
    TestBed.configureTestingModule({});
    injector = TestBed.inject(Injector);
  });

  it('should run effect function when trigger signal is true', fakeAsync(() => {
    const delay = signal<boolean>(false);
    const trigger = signal<boolean>(false);

    const chainer = chainEffect(initialEffectFn);
    const chainedEffect = chainer.configure({ delay, trigger, injector });

    // 调用run注册effect,此时回调尚未执行
    chainedEffect.run(effectFn);
    expect(initialEffectFn).toHaveBeenCalled();

    // 更新trigger信号为true,触发effect检测变更
    trigger.set(true);
    // 强制执行所有异步任务(包括effect的调度逻辑)
    tick();

    // 验证目标effect函数被调用
    expect(effectFn).toHaveBeenCalled();
  }));

  it('should throw error when both delay and trigger are false', fakeAsync(() => {
    const delay = signal<boolean>(false);
    const trigger = signal<boolean>(false);

    const chainer = chainEffect(initialEffectFn);
    const chainedEffect = chainer.configure({ delay, trigger, injector });

    // 异步执行的effect需要在tick后捕获错误
    expect(() => {
      chainedEffect.run(effectFn);
      tick();
    }).toThrowError('error');

    expect(initialEffectFn).toHaveBeenCalled();
  }));

  it('should do nothing when delay signal is true', fakeAsync(() => {
    const delay = signal<boolean>(true);
    const trigger = signal<boolean>(true);

    const chainer = chainEffect(initialEffectFn);
    const chainedEffect = chainer.configure({ delay, trigger, injector });

    chainedEffect.run(effectFn);
    tick();

    // 验证目标effect函数未被调用
    expect(effectFn).not.toHaveBeenCalled();
  }));
});

关键说明

  • fakeAsync + tick():模拟异步执行环境,tick()会强制触发所有待处理的异步任务,确保effect回调被执行。
  • Signal更新时机:注册effect后再更新Signal值,让effect检测到信号变更并执行对应逻辑。
  • Injector获取:通过TestBed.inject(Injector)获取测试环境中的Injector,避免手动创建导致的依赖异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 03:34:53