Ngrx测试:如何配置TestBed实例化完整状态存储并开展集成测试
我完全懂你想简化测试流程的心情——单独拆测effects、reducers确实太耗时间,直接端到端验证整个数据流链路才是高效的方案!下面就给你一套适配Angular 5 + NgRx 4的集成测试方案,能一次性覆盖action、effects、WebAPI、模拟服务器和选择器的协同工作,满足你“只关心页面是否失败”的需求。
核心思路
我们的目标是模拟真实用户触发操作后的完整数据流:触发NgRx Action → Effects监听Action并发起API请求 → 模拟后端返回数据 → Effects分发Success Action → Reducer更新状态 → 选择器读取状态 → 组件渲染内容。只要这个链路跑通,就说明整个流程没问题;如果测试失败,直接定位到链路异常即可,不用逐个拆测。
具体实现步骤
1. 配置测试依赖
首先在测试文件中导入必要的模块,包括Angular测试模块、NgRx的Store和Effects模块,以及用于模拟API的HttpClientTestingModule:
import { TestBed, async, ComponentFixture, fakeAsync, tick } from '@angular/core/testing'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { StoreModule, Store } from '@ngrx/store'; import { EffectsModule } from '@ngrx/effects'; // 导入你的项目中的reducers、effects、actions、selectors和组件 import { rootReducers } from '../store/reducers'; import { UserEffects } from '../store/effects/user.effects'; import { LoadUsers, LoadUsersSuccess } from '../store/actions/user.actions'; import { selectAllUsers } from '../store/selectors/user.selectors'; import { UserListComponent } from './user-list.component';
2. 初始化测试环境
在beforeEach中配置测试模块,注入Store和模拟API控制器:
describe('UserListComponent 集成测试', () => { let component: UserListComponent; let fixture: ComponentFixture<UserListComponent>; let store: Store; let httpMock: HttpTestingController; beforeEach(async(() => { TestBed.configureTestingModule({ imports: [ HttpClientTestingModule, // 用于模拟API请求 StoreModule.forRoot(rootReducers), // 注册NgRx根reducer EffectsModule.forRoot([UserEffects]) // 注册需要测试的effects ], declarations: [ UserListComponent ] // 声明要测试的组件 }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(UserListComponent); component = fixture.componentInstance; store = TestBed.inject(Store); httpMock = TestBed.inject(HttpTestingController); fixture.detectChanges(); // 触发初始变更检测 }); afterEach(() => { httpMock.verify(); // 确保所有API请求都被处理,避免测试泄漏 });
3. 编写集成测试用例
这个测试会完整覆盖整个数据流链路,最后通过检查组件渲染结果来判断流程是否正常:
it('应该通过NgRx完整链路加载并展示用户数据', fakeAsync(() => { // 1. 定义模拟的API返回数据 const mockUsers = [ { id: 1, name: 'Adrian' }, { id: 2, name: 'Test User' } ]; // 2. 触发加载用户的Action store.dispatch(new LoadUsers()); // 3. 拦截API请求并返回模拟数据 const apiReq = httpMock.expectOne('/api/users'); // 匹配你的真实API路径 expect(apiReq.request.method).toBe('GET'); apiReq.flush(mockUsers); // 4. 等待异步操作完成(API请求、Effects处理、状态更新) tick(); // 5. 验证选择器返回的状态是否正确 let loadedUsers; store.select(selectAllUsers).subscribe(users => loadedUsers = users).unsubscribe(); expect(loadedUsers).toEqual(mockUsers); // 6. 验证页面是否正确渲染数据(核心:判断页面是否失败) fixture.detectChanges(); // 更新组件模板 const userItems = fixture.nativeElement.querySelectorAll('.user-item'); expect(userItems.length).toBe(2); expect(userItems[0].textContent).toContain('Adrian'); })); });
关键细节说明
- 模拟API替代真实后端:用
HttpClientTestingModule和HttpTestingController拦截API请求,不需要额外启动mock服务器(比如json-server),测试更轻量。 - 异步处理:用
fakeAsync和tick()来处理API请求、Effects的异步逻辑,确保状态更新完成后再做断言。 - 聚焦页面结果:最后通过检查组件的DOM元素来判断流程是否正常,完全符合你“只需知晓页面是否失败”的需求,不用纠结是effects还是reducer的问题。
如果你不需要验证组件,只想单独验证Store、Effects和API的协同,也可以去掉组件相关代码,直接断言Store的状态即可:
it('应该在API请求成功后更新Store状态', fakeAsync(() => { const mockUsers = [{ id: 1, name: 'Adrian' }]; store.dispatch(new LoadUsers()); const apiReq = httpMock.expectOne('/api/users'); apiReq.flush(mockUsers); tick(); let loadedUsers; store.select(selectAllUsers).subscribe(users => loadedUsers = users).unsubscribe(); expect(loadedUsers).toEqual(mockUsers); }));
内容的提问来源于stack exchange,提问作者Adrian Moisa
相关产品推荐
相关产品推荐

