Exploring Unit and Integration Testing in Redux Saga

Oleg Talaver
The Startup
Published in
4 min readJul 15, 2020

Redux is an extremely useful library that facilitates managing of an app’s state. Among many middlewares, Redux-Saga fits me the best as in a React-Native project I’m working at the moment, I’ve had to deal with lots of side effects that would bring me endless headaches in case I put them in components. With the tool, the creation of complex flows becomes straightforward. But what about the testing? Is this as smooth as the usage of the library? While I can’t give you the exact answer, I’m to show you the real example of problems that I faced.

If you’re not familiar with testing of sagas, I recommend reading a dedicated page in the docs. In the following examples, I use redux-saga-test-plan as it gives me the full strength of integration testing alongside with unit testing.

A bit about unit testing

Unit testing is nothing more than testing of a small piece of your system, usually a function, that has to be isolated from other functions and, which is more important, from APIs.

Going ahead I’d say that I haven’t seen the point of unit testing in my project yet. I moved all the business logic and APIs abstractions into external modules to let the sagas handle only the flow of the application. So I haven’t had those big sagas that I couldn’t split into smaller, clearly see what they do (they’re quite self-explanatory).

Example of a saga
Unit test for the saga

There you can see the usual way to check our effect creators. If there had been any API calls that, I would’ve mocked them using `jest.fn`.

As we’ve done with tedious work, let’s proceed to the main course.

Integration testing

The significant drawbacks of unit testing are external calls. You need to mock them. In case your sagas consist only of these calls and no logic, testing step by step, while abstracting all dependencies, becomes a dull task. But what if we only want to check the flow without dealing with each of the effects. What if we need to test sagas in the context of the state, with the use of reducers. I have excellent news, that’s exactly what I wanted to share with you.

Test multiple sagas

Let’s consider the following example, which is an adapted version of the code from my project:

A couple of sagas to test

Here we have the root saga `sessionWatcher` that initialize application by calling `initApp` right away after loading and also waits for the action to load a project by id. The project is loaded from storage after which, we save the project to the state and call an external function that saves the session and loads a map. The example shows all sorts of problems we can stumble upon during the testing process: working with multiple sagas, accessing the state, calling APIs that we’d like to avoid.

Test multiple sagas

The test above tests all the sagas and split into few parts. The first part introduces objects that we’ll use to test our sagas, the second, is testing itself. We call `expectSaga` that’ll run the root saga and tests it against the checks listed below it.

The first function we see is `provide`, which uses matchers to locate the effect creators that will be mocked. The first tuple (pair of values) uses the effect creator from the Redux Saga library and matches exactly to `select` effect that calls `getProjectFromStorage` selector. If we want more flexibility, we can use matchers that are provided by Redux Saga Test Plan library as we do in the second tuple, where we say to match by function, ignoring its arguments. This mechanism allows us to avoid accessing the store or calling some functions and much, much more that I don’t list here.

After that, we have a chain of effect cheks. Note that we don’t have to put them in the specific order or include all the effects but rather list the effects that we expect to see. However, calls to dispatch must be in order.

The chain ends with `silentRun` function that does three things: runs our test, suppress timeout error and return a promise.

Simulate an error

To simulate an error, we can use already familiar providers and a helper function from `redux-saga-test-plan/providers` to replace an effect with an error.

Test sagas with an error simulation

Reducers and state

But what about the state, how would we test all together with reducers. With the library, the task is a no-brainer. Firstly, we need to introduce our reducer:

Example of a reducer

Secondly, we change our test a bit by adding `withReducer`, which allows us using dynamic state (you can provide state without reducer by calling `withState`), and `hasFinalState`, which compares the state with the expected one, functions.

Test the sagas with state

More on using the magic library here. Hope you enjoyed the reading, thank you.

--

--

Oleg Talaver
The Startup

I’m a student that once tried programming and have never stopped learning since then.