单元测试 NgRx 效果以确保调用服务方法 - 不起作用

Posted

技术标签:

【中文标题】单元测试 NgRx 效果以确保调用服务方法 - 不起作用【英文标题】:Unit testing NgRx effect to ensure the service method was called - ain't working 【发布时间】:2019-06-15 00:25:05 【问题描述】:

我正在使用 NgRx ^7.0.0 版本。 这是我的 NgRx 效果类:

import  Injectable  from '@angular/core';
import  ApisService  from '../apis.service';
import  Effect, Actions, ofType  from '@ngrx/effects';
import  Observable  from 'rxjs';
import  ApisActionTypes, ApisFetched  from './apis.actions';
import  mergeMap, map  from 'rxjs/operators';

@Injectable()
export class ApisEffects 

  constructor(private apisS: ApisService, private actions$: Actions)  

  @Effect()
  $fetchApisPaths: Observable<any> = this.actions$.pipe(
    ofType(ApisActionTypes.FetchApisPaths),
    mergeMap(() =>
      this.apisS.fetchHardCodedAPIPaths().pipe(
        map(res => new ApisFetched(res))
      )
    )
  );

这是一个简单的测试。如您所见,它应该失败,但总是通过。 我在 *** How to unit test this effect (with dispatch: false)? 上关注了类似的问题,但它对我不起作用,好像代码执行从未进入效果。$fetchApisPaths.subscribe 块

import  TestBed  from '@angular/core/testing';
import  provideMockActions  from '@ngrx/effects/testing';
import  hot, cold  from 'jasmine-marbles';
import  Observable, ReplaySubject  from 'rxjs';
import  ApisEffects  from '../state/apis.effects';
import  ApisFetch, ApisFetched  from '../state/apis.actions';
import  IApiPath  from '../models';
import  convertPaths, getAPIPathsAsJson, ApisService  from '../apis.service';
import  ApisServiceMock  from './mocks';

describe('Apis Effects', () => 
  let effects: ApisEffects;
  let actions: Observable<any>;
  let apisS: ApisService;

  beforeEach(() => 
    TestBed.configureTestingModule(
      providers: [
        ApisEffects,
        provideMockActions(() => actions),
        
          provide: ApisService,
          useClass: ApisServiceMock
        
      ]
    );

    effects = TestBed.get(ApisEffects);
    apisS = TestBed.get(ApisService);
  );

  it('should call ApisService method() to get Api Paths', () => 
    const spy = spyOn(apisS, 'fetchHardCodedAPIPaths');

    const action = new ApisFetch();
    actions = hot('--a-', a: action);

    effects.$fetchApisPaths.subscribe(() => 
      console.log('%c effect trigerred', 'color: orange; border: 1px solid red;');
      // expect(spy).toHaveBeenCalled();
      expect(true).toBe(false); // never fails
    );
  );
);

以防万一我对动作做傻事,这里是动作文件: 很可能我不是,因为它在应用程序中按预期工作。

import  Action  from '@ngrx/store';
import  IApiPath  from '../models';

export enum ApisActionTypes 
    FetchApisPaths = '[Apis] Fetch Paths',
    FetchedApisPaths = '[Apis] Fetched Paths'


export class ApisFetch implements Action 
    readonly type = ApisActionTypes.FetchApisPaths;


export class ApisFetched implements Action 
    readonly type = ApisActionTypes.FetchedApisPaths;
    constructor(public payload: IApiPath[]) 


export type ApisActions = ApisFetch | ApisFetched;

========================编辑======================= =======

我使用了来自官方 ngrx 文档https://ngrx.io/guide/effects/testing 的示例,现在我可以成功进入下面的订阅块,我记录了两个控制台日志,但测试成功。这很奇怪!我已经尝试从订阅块中抛出错误并且测试仍然成功。

it('should work also', () => 
    actions$ = new ReplaySubject(1);

    actions$.next(new ApisFetch());

    effects.$fetchApisPaths.subscribe(result => 
      console.log('will be logged');
      expect(true).toBe(false); // should fail but nothing happens - test succeeds
      console.log(' --------- after '); // doesn't get called, so the code
      // execution stops on expect above
    );
  );

【问题讨论】:

console.log 是否执行? 不,它没有。 确保 ApisServiceMock 返回一个 observable。 确实如此。 fetchHardCodedAPIPaths(): Observable&lt;IApiPath[]&gt; return of(convertPaths(getAPIPathsAsJson().paths)); 我用另一个测试用例更新了我的问题,其中代码执行进入 subscribe() 块,但测试仍然是绿色的。 【参考方案1】:

好的,所以我让它工作了。为了成功测试是否从 NgRx 效果中调用了特定的 Angular 服务方法,我在 async 中封装了一个测试用例:

  it('should call ApisService method to fetch Api paths', async () => 
    const spy = spyOn(apisS, 'fetchHardCodedAPIPaths');

    actions$ = new ReplaySubject(1);
    actions$.next(new ApisFetch());
    await effects.$fetchApisPaths.subscribe();
    
    expect(spy).toHaveBeenCalled();
  );

await effects.$fetchApisPaths.subscribe(); 阻止执行并在下一行运行测试断言。

现在当我尝试运行expect(true).toBe(false); 来测试测试是否失败时,它正确地失败了。

我的代码在问题中的问题(ReplaySubject 的示例,如 ngrx 文档 https://ngrx.io/guide/effects/testing 中的示例)是,当断言位于 .subscribe() 块内时,测试不可能失败。那里发生了一些不确定的事情,我仍然不知道为什么代码会以下列方式运行:

effects.$fetchApisPaths.subscribe(result => 
  console.log('will be logged');  // 1) gets logged
  expect(true).toBe(false);       // 2) should fail
  console.log(' - after ');       // 3) doesn't get called
);  

所以代码执行在 2) 行停止,测试用例返回正数,3) 行永远不会被执行。

因此,在 .subscribe() 块内带有断言的 ngrx 文档中的测试用例将始终为绿色,从而为您的测试用例提供误报。这是我在使用ngrx ^7.0.0 时遇到的行为

编辑 2020 年 9 月 - 针对 ngrx 版本 9 进行了更新。 如果上面的解决方案对您或将来对我不起作用,因为我再次面临同样的问题并且只找到我自己的答案来帮助和来自@Christian 的精彩评论以引导我到 ngrx gitter 问题,试试这个方法:

 it('should call ApisService method to fetch Api paths', async () => 
  const spy = spyOn(apisS, 'fetchHardCodedAPIPaths');

  actions$ = cold('--a-', 
      a: ControlCenterTrendsLineChartPeriodChange( numberOfMonths: 24 )
  );
  await effects.$fetchApisPaths.subscribe();

  expect(actions$).toSatisfyOnFlush(() => 
      expect(spy).toHaveBeenCalled();
);

【讨论】:

我一直成功地将 hot 用于流式操作,并且一直有效。只有这个用例,热或冷不起作用,只有 ReplaySubject 起作用。它非常有线,但谢谢! 我有一个similar problem,没有任何效果,我快要疯了。然后,一次偶然的机会,我打开了jest-marbles 的自述文件,并在那里找到了解决方案(测试ngrx 效果中的副作用):expect(stream).toSatisfyOnFlush(() =&gt; expect(someMock) .toHaveBeenCalled(); )。我已经发布了full solution on ngrx's Gitter chat。

以上是关于单元测试 NgRx 效果以确保调用服务方法 - 不起作用的主要内容,如果未能解决你的问题,请参考以下文章

如何调用两次在ngrx效果中调用的服务?

在单元测试中使用参数模拟 ngrx 存储选择器(Angular)

从ngrx效果调用API时捕获错误

单元测试:为了测试值随时间的变化而返回可观察数据以返回主题的模拟服务导致TS抛出TS2339

单元测试茉莉花@NGRX/DATA

NGRX - 使用 jasmine-marbles 将 Promise 转换为 observables 的测试效果问题