Angular NgRx单元测试失败:异步状态验证问题排查
Angular NgRx单元测试问题:Dispatch后异步条件验证失败
我无法理解为何我的Angular NgRx单元测试失败,想请教如何在Store Dispatch后验证异步条件是否已满足。我需要验证三个点:
- 当
isToastVisible$设为true后,组件的提示框可见; - 处于AddMode时,提示文本显示为'La note a été ajoutée !';
- 非AddMode时,提示文本显示为'La note a été modifiée !'。
我已使用fakeAsync测试异步行为,但未达预期效果,请问我遗漏了什么?
相关代码
HTML模板
<ng-container *ngIf="(isToastVisible$ | async)"> <div *ngIf="(isAddMode| async)" class="position-fixed bottom-0 start-50 translate-middle-x p-3 border border-success bg-success text-white" style="z-index: 11">La note a été ajoutée !</div> <div *ngIf="!(isAddMode | async)" class="position-fixed bottom-0 start-50 translate-middle-x p-3 border border-success bg-success text-white" style="z-index: 11">La note a été modifiée !</div> </ng-container>
组件TS代码
import { Component, OnInit } from '@angular/core'; import { select, Store } from '@ngrx/store'; import { Observable } from 'rxjs'; import { showAddEditNoteToast, AddEditNoteModalMode } from 'src/app/reducers/selectors/selectors'; @Component({ selector: 'app-toast', templateUrl: './toast.component.html', styleUrls: ['./toast.component.scss'] }) export class ToastComponent implements OnInit { public isToastVisible$: Observable<boolean>; public isAddMode$: Observable<boolean>; constructor( private store: Store ) { } ngOnInit(): void { this.isToastVisible$ = this.store.pipe(select(showAddEditNoteToast)); this.isAddMode$ = this.store.pipe(select(AddEditNoteModalMode)); } }
测试Spec.ts代码(原代码)
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; import { ToastComponent } from './toast.component'; import { MockStore, provideMockStore } from '@ngrx/store/testing'; import { Initialstate } from '../../app/reducers/index'; import { setFormMode, showAddEditNoteModal } from 'src/app/reducers/actions/actions'; import { By } from '@angular/platform-browser'; describe('ToastComponent', () => { let component: ToastComponent; let fixture: ComponentFixture<ToastComponent>; let store: MockStore; const initialState = Initialstate; beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [ToastComponent], providers: [ provideMockStore({ initialState }) ] }) .compileComponents(); store = TestBed.inject(MockStore) }); beforeEach(() => { fixture = TestBed.createComponent(ToastComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create Toast component ', fakeAsync(() => { store.dispatch(showAddEditNoteModal({ showAddEditNoteModal: true })); tick(); expect(component).toBeTruthy(); })); it('should render text - La note a été ajoutée !', fakeAsync(() => { store.dispatch(setFormMode({ isAddMode: true })); fixture.detectChanges(); tick(); const text = fixture.debugElement.nativeElement; expect(text).toEqual('La note a été ajoutée !') })) });
问题分析与修正方案
你的测试失败主要有以下几个原因,对应修正如下:
1. 测试逻辑与DOM渲染条件不匹配
HTML中提示框的渲染依赖两个异步条件:isToastVisible$为true,同时isAddMode$对应布尔值。原测试中要么只dispatch了一个动作,要么没有确保提示框的显示条件满足。
2. 变更检测与异步时机顺序错误
使用fakeAsync时,需要先通过tick()让NgRx的异步数据流完成状态更新,再调用fixture.detectChanges()触发视图更新,而不是先调用detectChanges。
3. DOM元素定位错误
原代码直接获取组件根元素,无法拿到提示框的文本内容,需要用By.css定位具体的提示div。
4. 无效测试逻辑
第一个测试验证component是否truthy没有意义,组件实例在创建后就已经存在,应该改为验证提示框在dispatch显示动作后可见。
修正后的完整测试代码
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; import { ToastComponent } from './toast.component'; import { MockStore, provideMockStore } from '@ngrx/store/testing'; import { Initialstate } from '../../app/reducers/index'; import { setFormMode, showAddEditNoteModal } from 'src/app/reducers/actions/actions'; import { By } from '@angular/platform-browser'; describe('ToastComponent', () => { let component: ToastComponent; let fixture: ComponentFixture<ToastComponent>; let store: MockStore; const initialState = Initialstate; beforeEach(async () => { await TestBed.configureTestingModule({ declarations: [ToastComponent], providers: [ provideMockStore({ initialState }) ] }).compileComponents(); store = TestBed.inject(MockStore); }); beforeEach(() => { fixture = TestBed.createComponent(ToastComponent); component = fixture.componentInstance; fixture.detectChanges(); // 触发初始变更检测 }); // 修正第一个测试:验证提示框在isToastVisible$为true时可见 it('should display toast when isToastVisible$ is true', fakeAsync(() => { // 先dispatch显示动作 store.dispatch(showAddEditNoteModal({ showAddEditNoteModal: true })); tick(); // 等待异步数据流完成状态更新 fixture.detectChanges(); // 触发视图更新 // 定位提示框容器 const toastContainer = fixture.debugElement.query(By.css('ng-container')); expect(toastContainer).toBeTruthy(); })); // 验证AddMode下的提示文本 it('should render "La note a été ajoutée !" in add mode', fakeAsync(() => { // 先设置AddMode,再设置提示框可见 store.dispatch(setFormMode({ isAddMode: true })); store.dispatch(showAddEditNoteModal({ showAddEditNoteModal: true })); tick(); // 等待状态更新完成 fixture.detectChanges(); // 更新视图 // 定位提示文本元素 const toastText = fixture.debugElement.query(By.css('div')).nativeElement.textContent.trim(); expect(toastText).toEqual('La note a été ajoutée !'); })); // 编辑模式下的提示文本 it('should render "La note a été modifiée !" in edit mode', fakeAsync(() => { store.dispatch(setFormMode({ isAddMode: false })); store.dispatch(showAddEditNoteModal({ showAddEditNoteModal: true })); tick(); fixture.detectChanges(); const toastText = fixture.debugElement.query(By.css('div')).nativeElement.textContent.trim(); expect(toastText).toEqual('La note a été modifiée !'); })); });
额外注意点
如果你的初始状态中showAddEditNoteToast默认值为false,或者AddEditNoteModalMode默认值不符合测试预期,可以在测试前通过store.overrideSelector手动覆盖选择器返回值,比如:
// 在beforeEach或单个测试中添加 store.overrideSelector(showAddEditNoteToast, false); store.overrideSelector(AddEditNoteModalMode, false); store.refreshState(); // 刷新状态让覆盖生效
内容的提问来源于stack exchange,提问作者allan00958
相关产品推荐
相关产品推荐

