Pascal
Pascal

Reputation: 75

How to test NgRx Effect that uses event from dependency injected service

I have following effect that uses the an observable source from a PlatformService provided by Angulars Dependency Injection.

public resume$ = createEffect(() => {
  return this.platformService.resume().pipe(mapTo(PlatformActions.appResumed()));
});

constructor(private platformService: PlatformService) {
}

This works really well, but I do not really know how to test it elegantly. Usually we try to setup our TestingModule as unspecific as possible. e.G

describe('PlatformEffects', () => {
  let effects: PlatformEffects;
  let platformServiceSpy: SpyObj<PlatformService>;

  beforeEach(() => {
    platformServiceSpy = jasmine.createSpyObj({
      resume: EMPTY
    });

    TestBed.configureTestingModule({
      providers: [
        PlatformEffects,
        {
          provide: PlatformService,
          useValue: platformServiceSpy
        }
      ]
    });

    effects = TestBed.inject(PlatformEffects);
  });

  it('should be created', () => {
    expect(effects).toBeTruthy();
  });

  describe('resume$', () => {
    it('dispatches appResumed action on resume event', (done) => {
      // AAA: This test should setup the response from the platformService so the arrange setup is coupled in the test.
    });
  });
});

Sadly this does not seem to work as the effects are created in the constructor and therefore my event stream is always EMPTY and does not trigger the effect even if I overwrite the resume response in my spy.

Now I could set a default value in the beforeEach (e.G. of(undefined)) but then it is not really coupled in my tests and I cant use the AAA pattern.

On the other hand I could probably create a new Effects instance every time but that seems a bit overkill not?

Is there a better method? NgRx solved it really well with the actions stream and I wonder if there is a similar solution for effects using sources from DI.

Upvotes: 1

Views: 292

Answers (2)

Pascal
Pascal

Reputation: 75

I tried a bit more and thought that the actions stream is probably just a subject and I could do the same for my own service method:

describe('PlatformEffects', () => {
  let effects: PlatformEffects;
  let platformServiceSpy: SpyObj<PlatformService>;

  let resumeSubject: ReplaySubject<any>;

  beforeEach(() => {
    // create new subject that can be used as an event emitter
    resumeSubject = new ReplaySubject<any>();

    platformServiceSpy = jasmine.createSpyObj({
      resume: resumeSubject.asObservable()
    });

    TestBed.configureTestingModule({
      providers: [
        PlatformEffects,
        {
          provide: PlatformService,
          useValue: platformServiceSpy
        }
      ]
    });

    effects = TestBed.inject(PlatformEffects);
  });

  it('should be created', () => {
    expect(effects).toBeTruthy();
  });

  describe('resume$', () => {
    it('dispatches appResumed action on resume event', (done) => {
      // emit new test string in resume subject
      resumeSubject.next('test');

      effects.resume$.subscribe((res) => {
        expect(res).toEqual(PlatformActions.appResumed());
        done();
      });
    });
  });
});

This works for this case but as soon as I try to do the same with a service method that returns a promise this solution does not work anymore.

Upvotes: 0

timdeschryver
timdeschryver

Reputation: 15505

For more info see, https://timdeschryver.dev/blog/testing-an-ngrx-project#effects


it('fetch$ dispatches a success action', () => {
    // 🔦 The Effect Actions stream is created by instantiating a new `ActionsSubject`
    const actions = new ActionsSubject();
    const effects = new CustomersEffects(actions, newCustomerService());

    // 🔦 Subscribe on the effect to catch emitted actions, which are used to assert the effect output
    const result: Action[] = [];
    effects.fetch$.subscribe((action) => {
        result.push(action);
    });

    const action = customerPageActions.enter({ customerId: '3' });
    actions.next(action);

    expect(result).toEqual([
        customersApiActions.fetchCustomerSuccess(
            newCustomer({
                id: action.customerId,
            }),
        ),
    ]);
});

Upvotes: 1

Related Questions