edcs
edcs

Reputation: 3879

Unit Testing Redux Sagas

I've followed the documentation for Redux Sagas and created a set of tests, however they seem extremely brittle to me. Whenever I update the logic in any of my sagas, my tests fail. This raises alarm bells in my head and leads me to think that I'm doing something wrong.

Here's an example saga which communicates with an API to load a user's profile and store it in the Redux state:

export function* userAuthCheck() {
    try {
        yield put({ type: START_HTTP });

        const me = yield apply(user, user.me);
        const organisations = yield apply(user, user.organisations, [me.body.data.id]);

        yield put({
            type: USER_AUTH_SUCCESS,
            redirect: true,
            state: {
                account: me.body.data,
                organisations: organisations.body.data,
            },
        });
    } catch (error) {
        yield put({ type: USER_AUTH_REFRESH });
    } finally {
        yield put({ type: COMPLETE_HTTP });
    }
}

Here's the corresponding test:

it('can successfully call the API', () => {
    const generator = userAuthCheck();

    expect(generator.next().value).toEqual(put({ type: START_HTTP }));
    expect(generator.next().value).toEqual(call(user.me));

    expect(generator.next().value).toEqual(put({
        type: USER_AUTH_SUCCESS,
        state: { user: undefined },
    }));

    expect(generator.next().value).toEqual(put({ type: COMPLETE_HTTP }));
});

You might notice that the test will actually fail if run because I've not updated it since I made some recent updates to the saga. This is the sort of thing which started me on this trail of thought.

Is there a better way to test my sagas? Is it possible to run them from start to finish, mock out different API responses and assert that the right things get dispatched into state?

These are the docs I've been following: https://redux-saga.github.io/redux-saga/docs/introduction/BeginnerTutorial.html

Upvotes: 4

Views: 2446

Answers (2)

Carlos C
Carlos C

Reputation: 677

I like testing it by mocking the store, so for your example it'd be something like:

import configureMockStore from "redux-mock-store";
import createSagaMiddleware from "redux-saga";
import rootSaga from "pathToRootSaga/rootSaga";
import {userAuthCheckActionCreator} from "yourPath/UserActionCreators"; 

it('can successfully call the API', () => {
  const sagaMiddleware = createSagaMiddleware();
  const mockStore = configureMockStore([sagaMiddleware]);
  const store = mockStore({});
  sagaMiddleware.run(rootSaga);

  const expectedActions = [
    {
      type: START_HTTP
    },
    {
      type: USER_AUTH_SUCCESS,
      redirect: true,
      state: {
          account: me.body.data,
          organisations: organisations.body.data,
      }
    },
    { 
      type: COMPLETE_HTTP 
    }
  ];

  store.dispatch(userAuthCheckActionCreator());
  expect(store.getActions()).toEqual(expectedActions);
});

What I like about this approach is that I can clearly see if the whole sequence of actions are getting dispatched in the expected order. Plus, everything happens within the context of the mocked store letting the Saga middleware dispatch the actions, so you don't need to manipulate the generator function.

Upvotes: 3

erikvold
erikvold

Reputation: 16508

There is a long issue thread about this in the redux-saga issue tracker for issue #518. So there isn't an agreed upon right way to test, there are a few options out there though, and I would say that the best approach might depend on exactly what it is you are testing.

There are a few test helper packages mentioned in the above thread there like:

There are also some good examples in the redux-saga test suite to learn from.

Upvotes: 1

Related Questions