如何为Angular中使用Ngrx select的组件逻辑编写单元测试
NgRx Store 选择逻辑单元测试指导
原测试存在的问题
你当前写的测试用例存在两个核心问题,会导致断言失败:
- 直接对
store.select用callThrough(),如果测试模块没有配置模拟状态,select('loggedUser')返回的Observable不会主动 emit 值,订阅回调不会触发,component.loggedUser不会被赋值,第二条断言直接不通过 - 依赖真实Store实现,不符合单元测试的隔离原则,测试稳定性差
正确测试实现方案
推荐两种常用的实现方式,可根据你的项目依赖选择:
方案1:使用NgRx官方MockStore(推荐)
官方提供的MockStore工具专门用于NgRx相关逻辑测试,不需要额外写模拟逻辑,适配性最高。
前置测试模块配置
import { MockStore, provideMockStore } from '@ngrx/store/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing'; // 导入你自己的组件 import { YourComponent } from './your.component'; describe('YourComponent', () => { let component: YourComponent; let fixture: ComponentFixture<YourComponent>; let store: MockStore; // 自定义测试用的初始状态 const mockInitialState = { loggedUser: { id: 1, username: 'test_user', email: 'test@example.com' } }; beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [YourComponent], providers: [ // 用MockStore替代真实Store,注入初始状态 provideMockStore({ initialState: mockInitialState }) ] }).compileComponents(); store = TestBed.inject(MockStore); fixture = TestBed.createComponent(YourComponent); component = fixture.componentInstance; });
测试用例代码
it('should get loggedUser from store when init', () => { // 触发ngOnInit生命周期 component.ngOnInit(); // 验证select方法被正确调用,参数匹配 expect(store.select).toHaveBeenCalledOnceWith('loggedUser'); // 验证loggedUser被正确赋值 expect(component.loggedUser).toEqual(mockInitialState.loggedUser); }); });
方案2:手动模拟Store(无NgRx测试工具依赖)
如果你的项目没有引入@ngrx/store/testing包,直接手动模拟select方法的返回值即可:
import { of } from 'rxjs'; // 其他必要导入省略... it('should get loggedUser from store when init', () => { // GIVEN:定义模拟用户数据 const mockUser = { id: 1, username: 'test_user', email: 'test@example.com' }; // 模拟store.select返回主动emit mockUser的Observable const selectSpy = spyOn(component.store, 'select').and.returnValue(of(mockUser).pipe(take(1))); // WHEN component.ngOnInit(); // THEN expect(selectSpy).toHaveBeenCalledOnceWith('loggedUser'); expect(component.loggedUser).toEqual(mockUser); });
注意事项
- 你的原逻辑里用了
pipe(take(1))只会取第一个emit的值,上述两种方案都匹配该逻辑,不需要额外处理 - 如果你用的是feature selector,只需要把
store.select的入参换成你对应的selector函数即可 - MockStore方案扩展性更强,如果后续需要测试状态变更的场景,直接调用
store.setState()更新状态即可测试
内容的提问来源于stack exchange,提问作者Miomir Dancevic
相关产品推荐
相关产品推荐

