Search code examples
reactjsreduxredux-sagaredux-saga-test-plan

ExpectSaga put assertion failes with multiple put effects


The saga has multiple puts.

export function* changeItemsSaga(action) {
    const prerequisite1 = yield select(prerequisite1Selector);

    // some processing

    yield put(actions.myAction1(payload1));

    // some more processing

    const prerequisite2 = yield select(prerequisite2Selector);

    // some more processing

    yield put(actions.myAction2(payload2));
}

How do I write my put expectations when the saga is returning multiple effects?

it('should update items', () =>
    expectSaga(sagas.changeItemsSaga, action)
        .provide([
            [select(prerequisite1), {}],
            [select(prerequisite2), {}],
        ])
        .put([actions.myAction1(payload1), actions.myAction2(payload2)])
        .run());

ExpectSaga unit test returns the following error:

Saga test error:
put expectation unmet:

Expected
--------
{ channel: null,
  action:
   [ { type: 'MY_ACTION_1',
       payload: { myItem1: [Object] } },
     { type: 'MY_ACTION_2',
       payload: { itemCollection: [Object] } } ] }

Actual:
------
1. { channel: null,
  action:
   { type: 'MY_ACTION_1',
     payload:
      { myItem1:
         { index: 0,
           childItem1: [Object],
           childItem2: [Object] } } } }
2. { channel: null,
  action:
   { type: 'MY_ACTION_2',
     payload: { itemCollection: [ [Object] ] } } }

This one doesn't work either.

it('should update items', () =>
    expectSaga(sagas.changeItemsSaga, action)
        .provide([
            [select(prerequisite1), {}],
            [select(prerequisite2), {}],
        ])
        .put(actions.myAction1(payload1))
        .put(actions.myAction2(payload2))
        .run());

It returns this error.

Saga test error:
put expectation unmet:

Expected
--------
{ channel: null,
  action:
   { type: 'MY_ACTION_1',
     payload:
      { myItem1:
         { index: 0,
           childItem1: [Object],
           childItem2: [Object] } } } }

Actual:
------
1. { channel: null,
  action:
   { type: 'MY_ACTION_1',
     payload:
      { myItem1:
         { index: 0,
           childItem1: [Object],
           childItem2: [Object] } } } }
2. { channel: null,
  action:
   { type: 'MY_ACTION_2',
     payload: { itemCollection: [ [Object] ] } } }

Solution

  • The provide syntax allows you to test the final effect of the saga instead of bootstrapping all of your state.

    Here's an example from the redux-saga docs -

    test('test only final effect with .provide()', () => {
      /*
      * With the .provide() method from expectSaga
      * you can by pass in all expected values
      * and test only your saga's final effect.
      */
      return expectSaga(callApi, 'url')
        .provide([
          [select(selectFromState), selectedValue],
          [call(myApi, 'url', selectedValue), response]
        ])
        .put(success(response))
        .run();
    });
    

    By invoking the actions call 2x, the test is running in succession, and the put in the middle of sagas.changeItemsSaga is effectively skipped.

    It looks like the function which you might be after is testSaga which will give you more fine-grained insight into the flow of the effects.