NgRx Effects单元测试中expect语句未执行的原因排查
NgRx Effects单元测试订阅未触发、断言不执行问题解决
问题根源
你的effect通过concatLatestFrom获取store.select(selectProjectIdFromRoute)的值,再通过filter(([_, projectId]) => !projectId)过滤流。但测试中未Mock这个选择器的返回值,默认情况下该选择器可能返回非空值,导致filter条件不满足,整个流没有任何输出——这意味着订阅回调根本不会执行,断言代码从未运行,jest自然判定测试通过(无失败断言)。
解决步骤
1. Mock Store选择器的返回值
使用NgRx官方的provideMockStore工具,在测试配置中指定selectProjectIdFromRoute选择器的返回值为null/undefined,确保filter条件放行:
import { provideMockStore } from '@ngrx/store/testing'; // 在TestBed配置中添加 TestBed.configureTestingModule({ providers: [ YourEffectsClass, provideMockStore({ selectors: [ { selector: selectProjectIdFromRoute, value: null // 让filter条件(!projectId)成立 } ] }), MockProvider(ProjectDetailsService), // 其他必要依赖 ] });
2. 确保测试等待异步流执行
原测试的订阅回调是异步的,可能在断言执行前测试就已结束。推荐用fakeAsync+tick同步触发所有异步操作:
it("should call initialNavigateToProject if project is not opened yet", fakeAsync(() => { const projectId = 'test-id'; const action = invokeJoinProjectSuccess({ projectId, activeUsers: [] }); const serviceSpy = jest.spyOn(projectDetailsService, "initialNavigateToProject"); actions$ = of(action); // 订阅effect流 effects.navigateToProjectOnInvokeJoinProject$.subscribe(); // 触发所有异步管道操作 tick(); // 直接断言,无需在订阅回调内 expect(serviceSpy).toHaveBeenCalledTimes(1); expect(serviceSpy).toHaveBeenCalledWith(projectId); }));
或者用async/await结合take(1)等待流完成:
import { take } from 'rxjs'; it("should call initialNavigateToProject if project is not opened yet", async () => { const projectId = 'test-id'; const action = invokeJoinProjectSuccess({ projectId, activeUsers: [] }); const serviceSpy = jest.spyOn(projectDetailsService, "initialNavigateToProject"); actions$ = of(action); // 等待流发出第一个值后完成 await effects.navigateToProjectOnInvokeJoinProject$.pipe(take(1)).toPromise(); expect(serviceSpy).toHaveBeenCalledTimes(1); expect(serviceSpy).toHaveBeenCalledWith(projectId); });
额外说明
为什么原来的测试即使放expect(1).toEqual(2)也会通过?因为流没有发出任何值,订阅回调从未执行,jest不会运行任何断言,所以默认判定测试通过。
内容的提问来源于stack exchange,提问作者mzeitz
相关产品推荐
相关产品推荐

