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

Ngrx Effects登录测试问题求助:报错排查与教程推荐

如何测试NgRx Auth Effects并解决测试报错问题

我看到你在测试NgRx Auth Effects时遇到了Error: Expected $.length = 0 to equal 1的问题,这是因为当前测试没有正确模拟AuthService的login方法,导致Effect无法发出预期的动作。下面我会一步步修正你的测试代码,同时解释每个部分的逻辑,最后给你一些实用的NgRx Effects测试教程要点。


首先,先修正你的测试代码(解决报错)

我们需要模拟AuthService,避免调用真实的HTTP请求,同时控制它的返回值来覆盖不同的测试场景:

import { HttpClientTestingModule } from "@angular/common/http/testing";
import { TestBed } from "@angular/core/testing";
import { provideMockActions } from "@ngrx/effects/testing";
import { Observable } from "rxjs";
import { hot, cold } from "jasmine-marbles";
import { Credentials, AuthToken } from "../../models";
import { CONFIG, Config } from "../../../../config/config.module";
import { AuthService } from "../../services";
import { AuthEffects } from "./auth.effects";
import * as fromActions from "../actions";
import { Go } from '../../../router';

const credentials: Credentials = { email: "io@io.io", password: "abc" };
const error: any = { status: 401, message: "401 Unauthorized Error" };
const token: AuthToken = { token: "abc", expiresIn: 1546356028904 };

describe("AuthEffects", () => {
  let effects: AuthEffects;
  let actions: Observable<any>;
  // 声明mock的AuthService
  let authServiceSpy: jasmine.SpyObj<AuthService>;

  beforeEach(() => {
    // 创建AuthService的spy对象,包含login方法
    authServiceSpy = jasmine.createSpyObj('AuthService', ['login']);

    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [
        // 提供mock的AuthService代替真实服务
        { provide: AuthService, useValue: authServiceSpy },
        { provide: CONFIG, useClass: Config },
        AuthEffects,
        provideMockActions(() => actions)
      ]
    });

    effects = TestBed.inject(AuthEffects);
  });

  // 测试登录成功场景
  it('should emit LoginSuccess when login succeeds', () => {
    const action = new fromActions.Login({ credentials });
    const completion = new fromActions.LoginSuccess({ token });

    // 设置mock的login方法返回成功的Observable
    authServiceSpy.login.and.returnValue(cold('-a|', { a: token }));
    // 模拟触发Login动作
    actions = hot('-a', { a: action });
    // 预期延迟后发出LoginSuccess动作(和mock的login返回时序匹配)
    const expected = hot('--b', { b: completion });

    expect(effects.login$).toBeObservable(expected);
  });

  // 测试登录失败场景
  it('should emit LoginFailure when login fails', () => {
    const action = new fromActions.Login({ credentials });
    const completion = new fromActions.LoginFailure(error);

    // 设置mock的login方法返回错误的Observable
    authServiceSpy.login.and.returnValue(cold('-#', {}, error));
    actions = hot('-a', { a: action });
    const expected = hot('--b', { b: completion });

    expect(effects.login$).toBeObservable(expected);
  });

  // 测试登录成功后跳转路由的Effect
  it('should emit Go action when LoginSuccess is dispatched', () => {
    const action = new fromActions.LoginSuccess({ token });
    const completion = new Go({ path: ['/admin'] });

    actions = hot('-a', { a: action });
    const expected = hot('-b', { b: completion });

    expect(effects.loginSuccess$).toBeObservable(expected);
  });
});

测试逻辑解释

1. 核心概念:NgRx Effects测试的本质

NgRx Effects是监听动作流,然后触发异步操作(比如API请求),最后发出新动作的服务。测试Effects的核心是:

  • 模拟输入动作流(用provideMockActions和hot)
  • 控制依赖服务的返回值(比如这里的AuthService,用spy模拟)
  • 验证输出动作流是否符合预期(用toBeObservable和cold/hot)

2. 为什么之前的测试报错?

你之前的测试直接使用了真实的AuthService,它会发起真实的HTTP请求,但测试环境中没有后端服务,所以login方法返回的Observable永远不会发出值,导致login$ Effect没有任何输出,所以出现Expected $.length = 0 to equal 1的错误。

3. 关键代码解析

  • jasmine.createSpyObj:创建一个模拟的服务对象,我们只关心它的login方法,这样可以完全控制它的返回值,避免依赖真实后端。
  • hot和cold(jasmine-marbles):用来模拟Observable的时序:
    • hot('-a', {a: action}):表示在时间线的第1个时间单位发出action动作
    • cold('-a|', {a: token}):表示在第1个时间单位发出token,然后完成(|表示完成)
    • cold('-#', {}, error):表示在第1个时间单位抛出error(#表示错误)
  • 时序匹配:因为switchMap会等待login的Observable完成,所以我们需要让输入动作的时序和mock服务的返回时序对应,确保预期的输出动作时序正确。

实用NgRx Effects测试教程要点

1. 覆盖所有场景

每个Effect至少要测试三种场景:

  • 异步操作成功时的输出动作
  • 异步操作失败时的输出动作
  • (可选)异常分支处理(比如空输入、超时等)

2. 始终模拟依赖服务

永远不要在Effects测试中使用真实的服务(比如调用真实API),一定要用spy或者mock对象,这样测试才是可预测、快速、不依赖外部环境的。

3. 熟练使用jasmine-marbles

jasmine-marbles是测试RxJS Observable时序的神器,掌握它的语法:

  • -:时间单位(默认10ms)
  • a:发出值为a的事件
  • |:Observable完成
  • #:Observable抛出错误
  • ^:订阅开始的位置

4. 测试多个Effects的交互

如果你的Effects之间有依赖(比如loginSuccess$监听LoginSuccess动作),要单独测试每个Effect的独立逻辑,不要把多个Effect的测试混在一起。

5. 验证副作用

如果Effect有其他副作用(比如调用localStorage、导航),需要额外验证这些副作用是否被触发。比如在loginSuccess$的测试中,你可以模拟Router服务,验证navigate方法是否被调用。


内容的提问来源于stack exchange,提问作者Whisher

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 08:42:45