2

I am writing unit tests for my effect.ts file. But for some reason, it passes even if I change the expect statement to dispatch ContactInfoFailed action. Is there a problem with the subscription in my test file? I am unable to figure out what exactly is the root cause of this! Or if there is a better way to write this test? Please find below my effect and it's test case.

// effects.ts

import { Actions, Effect, ofType } from '@ngrx/effects';
import { ContactTriageService } from '../views/contact-triage/contact-triage.service';
import {
  CONTACT_INFO_RESPONSE_REQUESTED, ContactInfoSucceeded, ContactInfoFailed , 
  CONTACT_INFO_SELECTION_RESPONSE_REQUESTED, ContactInfoSelectionSucceeded, 
  ContactInfoSelectionFailed,
  ContactInfoServiceResponse
} from '../actions/actions';
import { mergeMap, map, catchError, delay } from 'rxjs/operators';
import { Injectable } from '@angular/core';
import { of } from 'rxjs';

@Injectable()
export class ContactInfoEffect {
  @Effect()
  fetchContactInfoResponse = this.actions.pipe(
    ofType(CONTACT_INFO_RESPONSE_REQUESTED),
    delay(250),
        mergeMap((action) =>
            this.contactTriageService.getContentInfo().
            pipe(map((contacts) => new ContactInfoSucceeded(contacts)),           
            catchError(() => of(new ContactInfoFailed()))
             ),
        )
  )

  constructor(
    private actions: Actions,
    private contactTriageService: ContactTriageService
  ) { }
}

// effects.spec.ts

import { TestBed } from '@angular/core/testing';
import { of, ReplaySubject, Subject, EMPTY } from 'rxjs';
import { StoreModule } from '@ngrx/store';
import { provideMockActions } from '@ngrx/effects/testing';
import { ContactInfoEffect } from './effects';
import { ContactTriageService } from '../views/contact-triage/contact-triage.service';
import { reducers } from '../reducers/reducers';
import { ContactInfoSucceeded } from '../actions/actions';
import { getContactInfoMock } from 'src/server/test/mocks';
import { ContactInfoResponse } from 'src/server/contact-info/interfaces/contact-info.interface';

describe('ContactInfoEffect', () => {
  let effects: ContactInfoEffect;
  let contactTriageService: ContactTriageService;
  let actions: Subject<any>;

  beforeEach(async () => {
    TestBed.configureTestingModule({
      imports: [StoreModule.forRoot(reducers)],
      providers: [
        ContactInfoEffect,
        {
          provide: ContactTriageService,
          useValue: {
            getContentInfo() {
              return EMPTY;
            }
          }
        },
        provideMockActions(() => actions)
      ]
    });
    effects = TestBed.get(ContactInfoEffect);
    contactTriageService = TestBed.get(ContactTriageService);
  });

  it('should dispatch `ContactInfoSucceeded` if the service returns successfully', () => {
    const response:ContactInfoResponse =  getContactInfoMock();
    const contactTriageServiceSpy = spyOn(
        contactTriageService,
      'getContentInfo'
    ).and.returnValue(of( [
        getContactInfoMock()
      ]));
    actions = new ReplaySubject(1);
    actions.next(new ContactInfoSucceeded(getContactInfoMock()));
    effects.fetchContactInfoResponse.subscribe((result) => {   
      expect(result).toEqual(new ContactInfoSucceeded(response));
    });
  });
});
R. Richards
  • 24,603
  • 10
  • 64
  • 64

1 Answers1

0

One reason that could make your test don't work as expected is the delay() operator. The delay() operator is making your stream/Effect totally asynchronous.

In order to correctly test an asynchronous stream like that you should make your test assertion async, or more recommended, you can schedule synchronously your stream by mocking the scheduler. Here are some useful links where is discussed and explained:

Testing and mocking lettable operators in RxJS 5.5

https://github.com/redux-observable/redux-observable/issues/180

https://github.com/angular/angular/issues/25457

As you can see, is a common problem, and the solution is not perfect in any case.

Tip: you can try to remove temporarily the delay() operator and see if it works.

Hope this helps!

  • Thanks for replying Llorenc, even if I remove delay it doesn't work correctly –  May 03 '19 at 14:22
  • Hmm interesting, can you share the 'test' output if there is? It think that maybe the problem is that the callback on the 'subscribe' is never called right? I think this line could be the problem: actions = new ReplaySubject(1); I mean, it seems it should go before the provideMockActions() function, since if you create a new Subject after, this new subject doesn't have any subscriber. – Llorenç Pujol Ferriol May 07 '19 at 07:21