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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 07:31:09