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'); });
解决方案
你的测试未按预期工作,核心原因有两点:
- Angular的
effect默认是异步调度的,直接调用run后,effect的回调不会立即执行。 - 初始
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
相关产品推荐
相关产品推荐

