this.action" /> this.action" /> this.action"/>

How to properly use jasmine-marbles to test multiple actions in ofType

1.6k Views Asked by At

I have an Effect that is called each time it recives an action of more than one "kind"

myEffect.effect.ts

  someEffect$ = createEffect(() =>
    this.actions$.pipe(
      ofType(fromActions.actionOne, fromActions.actionTwo),
      exhaustMap(() => {
        return this.myService.getSomeDataViaHTTP().pipe(
          map((data) =>
            fromActions.successAction({ payload: data})
          ),
          catchError((err) =>
            ObservableOf(fromActions.failAction({ payload: err }))
          )
        );
      })
    )
  );

in my test I tried to "simulate the two different actions but I always end up with an error, while if I try with one single action it works perfectly

The Before Each part

describe('MyEffect', () => {
  let actions$: Observable<Action>;
  let effects: MyEffect;
  let userServiceSpy: jasmine.SpyObj<MyService>;
  const data = {
  // Some data structure
  };
  beforeEach(() => {
    const spy = jasmine.createSpyObj('MyService', [
      'getSomeDataViaHTTP',
    ]);
    TestBed.configureTestingModule({
      providers: [
        MyEffect,
        provideMockActions(() => actions$),
        {
          provide: MyService,
          useValue: spy,
        },
      ],
    });

    effects = TestBed.get(MyEffect);
    userServiceSpy = TestBed.get(MyService);
  });

This works perfectly

    it('should return successActionsuccessAction', () => {
    const action = actionOne();
    const outcome = successAction({ payload: data });

    actions$ = hot('-a', { a: action });
    const response = cold('-a|', { a: data });
    userServiceSpy.getSomeDataViaHTTP.and.returnValue(response);

    const expected = cold('--b', { b: outcome });
    expect(effects.someEffect$).toBeObservable(expected);
  });

This doesn't work

it('should return successAction', () => {
const actions = [actionOne(), actionTwo()];
const outcome = successAction({ payload: data });
actions$ = hot('-a-b', { a: actions[0], b: actions[1] });
const response = cold('-a-a', { a: data });
userServiceSpy.getSomeDataViaHTTP.and.returnValue(response);
const expected = cold('--b--b', { b: outcome });
expect(effects.someEffect$).toBeObservable(expected);
});
1

There are 1 best solutions below

0
Anton Rusak On BEST ANSWER

There are two problems in this code.

  1. It suggests that getSomeDataViaHTTP returns two values. This is wrong, the response is no different from your first example: '-a|'
  2. It expects the second successAction to appear after 40 ms (--b--b, count the number of dashes). This is not correct, because actionTwo happens after 20 ms (-a-a) and response takes another 10 ms (-a). So the first successAction is after 20ms (10+10), the second is after 30ms (20+10). The marble is: '--b-b'.
Input actions     : -a -a
1st http response :  -a
2nd http response :     -a
Output actions    : --b -b

The working code:

it('should return successAction', () => {
  const actions = [actionOne(), actionTwo()];
  actions$ = hot('-a-b', { a: actions[0], b: actions[1] });

  const response = cold('-a|', { a: data });
  userServiceSpy.getSomeDataViaHTTP.and.returnValue(response);

  const outcome = successAction({ payload: data });
  const expected = cold('--b-b', { b: outcome });
  expect(effects.someEffect$).toBeObservable(expected);
});

Marble testing is cool but it involves some black magic you should prepare for. I'd very much recommend you to carefully read this excellent article to have a deeper understanding of the subject.