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

Angular @ngrx/effects单元测试:Action未清除问题排查与解决

问题原因分析

你遇到的问题核心在于测试用例间的状态污染:

  • 你在describe顶层定义了共享的action$变量,所有测试用例复用同一个引用。第一个测试将action$替换为of(...)创建的Observable,而provideMockActions传入的工厂函数始终指向这个共享变量。
  • Effects模块初始化时会持续订阅action$,而of创建的是冷Observable——每次有新订阅时,它都会重新发射所有内部值。后续测试执行时,新创建的Effects实例会再次订阅这个未被重置的action$,导致之前的abort action被重复触发。
  • 加上你给abort$ Effect设置了dispatch: false,它不会触发新的action,但自身的订阅不会自动终止,进一步加剧了跨测试的状态残留。
解决方法

1. 用Subject替代静态Observable,每个测试独立控制action发射

把action$改为Subject<Action>,并移到beforeEach中初始化,确保每个测试用例都有独立的action流:

describe('MyBankEffects', () => {
    let effects: MyBankEffects;
    let action$: Subject<Action>;

    beforeEach( () => {
        action$ = new Subject<Action>(); // 每个测试初始化新的Subject
        TestBed.configureTestingModule({
            imports: [
                EffectsModule.forFeature(MyBankEffects)
            ],
            providers: [
                provideMockActions( () => action$),
            ]
        });
        effects = TestBed.inject(MyBankEffects);
    });

    // 每个测试结束后完成Subject,清理订阅
    afterEach(() => {
        action$.complete();
    });

    it('should navigate to abort', (done) => {
        // 用next发射action,而非重新赋值action$
        action$.next(myActions.abort({ reason: AbortReason.TIMEOUT }));
        
        effects.abort$.pipe(take(1)).subscribe({
            next: () => {
                expect(...);
            },
            error: e => done.fail(e),
            complete: () => {
                expect(...);
                done();
            }
        });
    });
});

2. 避免共享变量,每个测试重新初始化action$

如果你不想用Subject,也可以在beforeEach中每次创建新的空Observable,测试用例中再替换为对应action流(这种方式灵活性较低,仅作补充):

describe('MyBankEffects', () => {
    let effects: MyBankEffects;
    let action$: Observable<Action>;

    beforeEach( () => {
        action$ = EMPTY; // 初始化为空Observable
        TestBed.configureTestingModule({
            imports: [
                EffectsModule.forFeature(MyBankEffects)
            ],
            providers: [
                provideMockActions( () => action$),
            ]
        });
        effects = TestBed.inject(MyBankEffects);
    });

    it('should navigate to abort', (done) => {
        action$ = of(myActions.abort({ reason: AbortReason.TIMEOUT }));
        effects.abort$.pipe(take(1)).subscribe({
            // ... 订阅逻辑
        });
    });
});

关键注意点

  • 不要在describe顶层定义会被修改的测试变量,否则必然导致测试用例间的状态污染。
  • 使用Subject可以精准控制action的发射、完成时机,是测试NgRx Effects的标准做法。
  • 每个测试结束后调用action$.complete(),可以终止所有相关订阅,彻底清理状态。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 02:07:17