[ironic tone] We all know that Redux is about functional programming, and when you have it functional — it is testable, right? You just have a lot of little functions doing very simple things that are deterministic.
Well, some of them are a bit more complex — like handling HTTP requests, but then you just mock (or nock) HTTP requests to test it…
Let me ask you this — have you ever had one of these thoughts while writing tests:
Do I really want to test this reducer? It just reflects a value from the action payload… Who writes a unit test for that?!
I only added one more store.dispatch({ type: HIDE_LOADER }) to this redux-thunk action… Of course it will assign false to the reducer! Why test it…
This fetchUsers action just sends the HTTP GET request and assigns a received body to the users reducer. The test will be more complex than its implementation! Skip.
Really, never? Are you a TDD purist? Are your deadlines realistic? Maybe, you think that 70% test coverage is for amateurs, and so each of your tiny reducers or actions have a *.spec.js file? Good for you!
However, if you are not from Narnia, you probably get what am I talking about. Sometimes, testing Redux seems an overhead and you fall into the test only the complex things mindset.
Well, I can relate to that from many reasons.. Let’s say we have a super simple React / Redux project, containing:
A reducer, that holds a list of users
Two actions
Some place to hold action types
A component with a button and a counter
A container to wire state to the component
So according to the recommendations of the redux.js.org in redux.js.org/docs/recipes/WritingTests.html our tests would look like this:
Actions’ test “by the book”
Reducer’s test “by the book”
Well, you probably noticed, that:
There are ~3 times more lines of code (LoC) that test actions than implement them
~2 times more LoC in the reducer test than in its implementation
We have not tested much here, just a … structure and the successful AJAX call.
You would not write such tests for every action and reducer out there,unless they have some complex business logic to support. Personally, I would not recommend you to.
Let’s see what other ways we have, to test the state part of React / Redux projects.
Component Integration Test
The given project files are all about a single thing — “users”. Why don’t we test all of these as a whole? We can load up an actual Redux store with our actions, reducer; we can load the react components via enzyme and wire that up with the store. Let’s take a look:
Let's sum up what we’ve done here:
We wrap our Users component with a Provider to inject a store that has all-Redux stuff within the users / feature catalogue only: <Provider store={store}><Users/></Provider>
We mount it with enzyme, so we can virtually click a button in the component
We nock the http request, so the http request in action works fine
We simulate a click on a button, that calls fetchUsers
We eventually expect an update in the component - users length, shown in the <span>{users.length}</span> to show the right count.
What is 'eventually'?
Once you click a button via enzyme, an action gets to the Redux store, updates the reducer so the component renders again and shows 2 instead of 0.
How do we know WHEN this happens? We have no callback or promise that we could rely on — we do not know when the action has been passed, when the reducer worked and when the component got rendered — it’s all async.
Therefore a decent pattern for testing such cases is 'eventually', that retries the expect function over and over again until it becomes what it has to, unless it times out.
What have we tested?
So we have an integration test that goes through the whole feature's layers and even tests how it integrates with React and Redux libraries.
Since there isn't much logic in actions / reducer / components / container as separate units, writing unit tests for them would be an overhead, BUT having a single test to test the whole flow seems big enough.
All right, what other options do we have?
State Integration Test
Out of five of our project files (container, component, actions, action types, and reducer), three are Redux-related. We can view these as a backend in our frontend application, i.e. as a separate unit, which is supposed to integrate with the Redux library.
Maybe we can test that separately? Maybe we can test if Users’ state is working as expected? Yes, we can:
What happened here:
We call an action: we cover everything in actions.js with tests
The action is dispatched on a real store combined with our reducer: we test action-types.js and reducer.js
Moreover, if we had selectors, we could get the state for assertion using them, and that also tests them
So by writing a single integration test we have all Redux layers (actions, reducer, action-types, selectors) covered for a single state.
Also, this looks like a big enough chunk to test — we know if all Users’ state parts integrates well within Redux.
Fitting the IT into your project’s structure
Up until now we’ve seen two types of integration tests:
Component Integration Test
State Integration Test
We could fit them into any project structure: either split by layer (with folders like components / containers / actions / reducers etc.) or split by feature — these two options, at least, are the most popular ones.
When we split by feature
Like here:
We can see that the User’s Component Integration Test would nicely fit into the Users/ folder:
If we did TDD, adding a new feature (e.g., Blogs ) could even start with a Component Integration Test blogs.spec.js that would cover the whole folder / feature.
If the feature does not have complex logic, maybe a single integration test for it would be more than enough? And there would be no need to write unit tests for each layer (component / container / actions / reducer) within the feature.
When we split by layer
We usually have the following folders: actions, reducers, action types, i.e. each Redux layer as a separate flattened folder.
Though a less popular way is this:
We can say that state is a layer, i.e. the root folder would have: state, containers, components etc.
When state is a separate, reusable layer, the State Integration Test we reviewed above, like state/users/users.spec.js, could be a single test for the whole Users’ state. Then every time we add a new state, like blogs or comments or ads we could just have a single integration test that covers the whole state feature.
Using this approach, State Integration Tests look organic within the structure, we just add one test file per folder for a unit, which should be worth testing.
One size fits all
Should we stop writing unit tests for the Redux part, and instead choose to do integration tests? Should we immediately restructure our project to be split by feature? Or introduce a state folder?
No, no and no.
First of all, it’s always project-specific. Maybe you need to have complex logic in reducers or your actions have lots of side effects (besides changing the state) that need to be checked. Then, maybe, you should aim for more unit tests. Your own project is always special and you know it best, right?
Martin Fowler suggests to have a lot of unit tests, a bit less service (integration) tests and the least UI (end-to-end), tests in our Test Pyramid (https://martinfowler.com/bliki/TestPyramid.html).
Though it’s probably better to have at least one integration test for your state or component, instead of not writing unit tests at all because their logic is too simple.
The general recommendation would be to mix techniques, try and use a bit of all test types, see what fits your project best. Your Redux part is definitely testable and worth testing! Just put some work to it in a smart way.
Photo by: Michał Parzuchowski on Unsplash
This post was written by Tomas Petras Rupšys
You can follow him on Twitter