Unit testing NgRx effect to ensure the service method was called - ain't working

时光毁灭记忆、已成空白 提交于 2019-12-08 05:08:10

问题


I am using NgRx ^7.0.0 version. This is my NgRx effect class:

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))
      )
    )
  );
}

And that's a simple test. As you can see it should fail, but is always passing. I followed similar question here on StackOverflow How to unit test this effect (with {dispatch: false})? but it doesn't work for me, as if the code execution never enters the effects.$fetchApisPaths.subscribe block

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
    });
  });
});

Just in case I am doing smthg stupid with actions, here is the actions file: Most likely I am not, since it's working in the app as expected.

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;

=======================EDIT==============================

I have used an example from official ngrx docs https://ngrx.io/guide/effects/testing and now I can successfully enter the subscribe block below, I get both console logs logged, but the test succeeds. This is bizarre! I have tried throwing errors from the subscribe block and the test still succeeds.

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
    });
  });

回答1:


Ok, so I got it working. In order to successfully test whether a specific Angular service method is called from within NgRx effect, I wrapped a test case in an 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();
  });

I await effects.$fetchApisPaths.subscribe(); to block the execution and run test assertion in the next line.

Now when I try to run expect(true).toBe(false); to test whether the test fails, it properly fails.

The problem with my code in the question (the example with ReplaySubject as in ngrx docs https://ngrx.io/guide/effects/testing ) was that it was not possible to fail a test when the assertion was inside .subscribe() block. Something iffy was going on in there and I still don't know exactly why the code was behaving in the following manner:

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
});  

So the code execution stops on line 2), test case returns positive and line 3) never gets executed.

So the test case in ngrx docs with assertion inside .subscribe() block will always be green, giving you a false positive for your test case. This is the behaviour I experienced with ngrx ^7.0.0



来源:https://stackoverflow.com/questions/54292921/unit-testing-ngrx-effect-to-ensure-the-service-method-was-called-aint-working

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!