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

如何对无类/可注入的NgRx函数式Effect进行单元测试?

测试NgRx函数式Effect的正确方法

问题背景

我需要对一个无类、无装饰器的NgRx函数式Effect进行单元测试,但尝试的测试代码无法触发Effect,请求有效的测试方案。

待测试的函数式Effect代码

export const loadUsers = createEffect(
  (actions$ = inject(Actions), usersService = inject(UsersService)) => {
    return actions$.pipe(
      ofType(userActions.getUser),
      exhaustMap(() => {
        return usersService.getAll().pipe(
          map((users) => userActions.usersLoadedSuccess({ users })),
        );
      })
    );
  },
  { functional: true }
);

失败的测试代码(Effect未触发)

import { HttpClientTestingModule } from '@angular/common/http/testing';
import { Observable, of } from 'rxjs';
import { TestBed } from '@angular/core/testing';
import { provideMockActions } from '@ngrx/effects/testing';
import { MockProvider } from 'ng-mocks';
import { Actions } from '@ngrx/effects';
import { userActions } from './user-store';
import { MockStore, provideMockStore } from '@ngrx/store/testing';
import { UsersService } from './users.service';
import { loadUsers } from './user.effects';

describe('Offer Effects', () => {
    let actions$: Observable<unknown>;
    let store: MockStore;
    let userService: UsersService;
    beforeEach(() => {
        TestBed.configureTestingModule({
            imports: [HttpClientTestingModule],
            providers: [loadUsers, MockProvider(UsersService), provideMockStore(), provideMockActions(() => actions$)],
        });

        actions$ = TestBed.inject(Actions);
        store = TestBed.inject(MockStore);
        userService = TestBed.inject(UsersService);
    });

    it('should test load users effect', async () => {
        actions$ = of({
            type: userActions.getUser,
        });
        const users = { name: 'Test', surname: 'Data' };
        jest.spyOn(userService, 'getAll').mockReturnValue(of(users));
        const spy = jest.spyOn(userActions, 'usersLoadedSuccess');
        await loadUsers;
        expect(spy).toHaveBeenCalledWith({ users });
    });
});

正确的测试方案

原测试的核心问题:

  • 错误地将loadUsers加入TestBed providers,函数式Effect无需依赖注入,可直接调用
  • 直接await loadUsers不会触发Effect流,需要订阅Effect的输出并手动发射action
  • 多余引入了MockStore(该Effect未依赖store)

修正后的测试代码

import { Observable, of, Subject } from 'rxjs';
import { TestBed } from '@angular/core/testing';
import { provideMockActions } from '@ngrx/effects/testing';
import { MockProvider } from 'ng-mocks';
import { Actions } from '@ngrx/effects';
import { userActions } from './user-store';
import { UsersService } from './users.service';
import { loadUsers } from './user.effects';
import { take } from 'rxjs/operators';

describe('User Effects', () => {
  let actions$: Subject<unknown>;
  let userService: UsersService;

  beforeEach(() => {
    actions$ = new Subject();

    TestBed.configureTestingModule({
      providers: [
        MockProvider(UsersService),
        // 使用Subject来控制action发射,替代原Observable
        provideMockActions(() => actions$)
      ]
    });

    userService = TestBed.inject(UsersService);
  });

  it('should dispatch usersLoadedSuccess when getUser is triggered', async () => {
    // 1. 模拟UsersService的返回值
    const mockUsers = { name: 'Test', surname: 'Data' };
    jest.spyOn(userService, 'getAll').mockReturnValue(of(mockUsers));

    // 2. 调用函数式Effect,传入mock的依赖
    const effect$ = loadUsers(actions$ as Actions, userService);

    // 3. 订阅Effect的输出,获取结果
    const resultPromise = effect$.pipe(take(1)).toPromise();

    // 4. 发射触发action
    actions$.next(userActions.getUser());

    // 5. 断言输出的action是否正确
    const emittedAction = await resultPromise;
    expect(emittedAction).toEqual(userActions.usersLoadedSuccess({ users: mockUsers }));
    expect(userService.getAll).toHaveBeenCalled();
  });
});

关键优化点

  • 使用Subject替代普通Observable,实现手动控制action的发射时机
  • 直接调用loadUsers函数,传入mock的actions$和usersService,无需依赖注入
  • 通过订阅Effect的输出流并转换为Promise,等待结果返回后断言
  • 移除无关的HttpClientTestingModule和MockStore,精简测试环境

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 18:35:28