19

ngrx and unit testing beginner here. I have the following effect:

@Injectable()
export class NotificationEffects {
  @Effect({dispatch: false})
  notificationShow$ = this.actions$
    .ofType(notificationAction.NOTIFICATION_SHOW)
    .do((action: notificationAction.NotificationShowAction) => {
      this.notificationService.info(action.payload.config);
    });

  constructor(private actions$: Actions, private notificationService: NotificationService) {}
}

Specifically, I would like to test that the notificationService method info has been called. How would I do that?

I have followed these examples but not found a solution:

https://netbasal.com/unit-test-your-ngrx-effects-in-angular-1bf2142dd459 https://medium.com/@adrianfaciu/testing-ngrx-effects-3682cb5d760e https://github.com/ngrx/effects/blob/master/docs/testing.md

camden_kid
  • 12,591
  • 11
  • 52
  • 88

5 Answers5

23

So it's as simple as this:

describe('notificationShow$', () => {
  let effects: NotificationEffects;
  let service: any;
  let actions$: Observable<Action>;
  const payload = {test: 123};

  beforeEach( () => {
    TestBed.configureTestingModule( {
      providers: [
        NotificationEffects,
        provideMockActions( () => actions$ ),
        {
          provide: NotificationService,
          useValue: jasmine.createSpyObj('NotificationService', ['info'])
        }
      ]
    } );

    effects = TestBed.get(NotificationEffects);
    service = TestBed.get(NotificationService);
  });

  it('should call a notification service method info with a payload', () => {
    actions$ = cold('a', { a: new notificationAction.NotificationShowAction(payload) });
    effects.notificationShow$.subscribe(() => {
      expect(service.info).toHaveBeenCalledWith(payload);
    });
  });
});
crackmigg
  • 5,571
  • 2
  • 30
  • 40
camden_kid
  • 12,591
  • 11
  • 52
  • 88
  • Thanks for the solution. I'm not quite clear when I should use cold & hot in the effects. In the above case, both cold & hot is working. – Manoj May 10 '18 at 09:37
  • 7
    I have the exact same setup and for some reason, the code in subscribe block never gets called. I even put `expect(true).toEqual(false);` in there and it never fails. Using ngrx ^7.0.0 – codeepic Jan 21 '19 at 14:39
  • @codeepic Probably worth creating a new question but that happens if you don't wrap a test within describe/it. – camden_kid Jan 21 '19 at 15:10
  • It is wrapped. I just opened the new question https://stackoverflow.com/questions/54292921/unit-testing-ngrx-effect-to-ensure-the-service-method-was-called-aint-working, cause I am just wasting my time here. – codeepic Jan 21 '19 at 15:19
  • Thanks for the solution. That was exactly what I was looking for. – Carsten Ennulat Mar 22 '19 at 09:33
  • 2
    I do agree with the above, this does not work! What works was the question in the comment above. You got to use an async some how, otherwise how would the test know it need to wait for the subscribe! – Mohy Eldeen Apr 05 '19 at 23:47
  • 1
    This isn't a very good example because if `effects.notificationShow$` doesn't emit at all then you never setup `expect(service.info)...` so it won't fail even when it should. – martin Feb 05 '20 at 14:57
  • Make sure that you wrap your it in waitForAsync() (used to be async()) anytime you subscribe to an observable. Subscribe is asynchronous, meaning the it completes before the expect is called. – Travis Peterson Jan 28 '21 at 15:56
8

The easiest (and officially suggested) way is to do it like this:

    it('should navigate to the customers detail page', () => {
      actions$ = of({ type: '[Customers Page] Customer Selected', name: 'Bob' });

      // create a spy to verify the navigation will be called
      spyOn(router, 'navigateByUrl');

      // subscribe to execute the Effect
      effects.selectCustomer$.subscribe();

      // verify the navigation has been called
      expect(router.navigateByUrl).toHaveBeenCalledWith('customers/bob');
    });

Here is the source.

Neurotransmitter
  • 6,289
  • 2
  • 51
  • 38
1

This should be helpful:

it('should call a notification service method info with a payload', () => {
    const action = new notificationAction.NotificationShowAction(payload);

    actions$ = hot('-a', { a: action });

    expect(effects.notificationShow$).toBeObserable(cold('-a', { a: action }))

    expect(actions$).toSatisfyOnFlush(() => {
      expect(service.info).toHaveBeenCalledWith(payload);
    });
  });
szmitas
  • 301
  • 2
  • 10
0

If someone interested without explicit subscription of effect and using only jasmine-marbles

it('should call a notification service method info with a payload', () => {
    actions$ = cold('a', { a: new notificationAction.NotificationShowAction(payload) });
    
    // `toBeObservable` will subscribe the effect and does the trick
    expect(effects.notificationShow$).toBeObservable(actions$);
    expect(service.info).toHaveBeenCalledWith(payload);
  });
Ravi Anand
  • 5,106
  • 9
  • 47
  • 77
-1
it('should call a notification service method info with a payload', () => {
    actions$ = cold('a', { a: new notificationAction.NotificationShowAction(payload) });
    effects.notificationShow$.subscribe(() => {
      expect(service.info).toHaveBeenCalledWith(payload);
    });
  });

It works well but the problem is when an error occur. In this case error is not reported to the test runner (to jest in my case). I need to add try catch block to get error:

   it('should call a notification service method info with a payload', () => {
        actions$ = cold('a', { a: new notificationAction.NotificationShowAction(payload) });
        effects.notificationShow$.subscribe(() => {
            try {
                expect(service.info).toHaveBeenCalledWith(payload); 
            } catch (error) {
                fail('notificationShow$: ' + error);
            }
        });
    });
David Sajdl
  • 164
  • 1
  • 10
  • 1
    At least in jest and i think in JS testing in general with async code you need either wrap in fakeAsync or use done method in the subscription, otherwise it will always pass – Rov Jul 07 '20 at 00:46