Everything should be made as simple as possible, but not simpler.
~ Albert Einstein
I prefer to apply the KISS Principle when solving problems, so the same goes for testing React-Redux apps.
I am building this testing piece upon my Idiomatic Redux article and app. Here, you can find the React Countries app with tests included that I will be referring to. After spending some time reading, thinking, and trying different approaches, I came up with a relatively simple, but efficient testing strategy. My goal was not to have a near 100% test coverage but rather to test what really matters without using too many resources (time, energy) and complex solutions.
The testing described in this article works with the following app setup/configuration:
- React 16
- Thunk middleware
Nevertheless, I am sure it can be adjusted for other setups.
What (Not) to Test
This is one of the most important questions – what to test and, more importantly, what not to test. My approach is to focus mostly on unit testing and some functional testing. Integration, E2E, and complex interaction testing is completely skipped and left for the QA team 😛
We will test:
- Dumb/Presentational React components – a minimalistic approach
- Thunk action creators
- Util/Common code
We will NOT test:
- Smart/Connected components
- Simple action creators
- Interaction between components
- Integration and E2E
The reasoning behind this decision is the following:
- We just want to make sure that presentational components actually render without exploding and test some simple things like: is a checkbox initially checked or are there 3
lielements if I pass in an array with 3 countries, etc.
- Smart connected components require a redux store, either mocked or a real one, which complicates things and is closer to integration testing that we want to avoid.
- Reducers are simple functions at the core of a Redux app, so they have to be thoroughly tested. Testing them is not difficult.
- Since simple (object) action creators are reaching reducers that are already tested, we don’t have to test these action creators again separately.
- We want to thoroughly test thunk action creators and make sure that when promises are resolved, we have proper actions (simple objects) trigger.
- All util/common code (functions) should be fully unit tested.
- If we want to test whether a button click in one component would trigger something within that same component – that’s ok. But as soon as we want to test interactions between different components that automatically goes more to integration testing and it’s not easy. Why? We can mount the root component in our test, simulate a click event and then assert and run the test, but as soon as we have several connected components in the tree (and we do), it complicates things, and we have to mock the store, pass it in with the Provider, etc.
There are many different tools and options out there. I did some research, and this is my arsenal of choice:
- Jest – the default test runner provided by create-react-app by Facebook
- Jasmine – enables you to write assertions and it’s included with Jest and create-react-app
- Enzyme – AirBnb’s library for testing React components
- Redux Mock Store – a library for mocking a Redux store
- Nock – an HTTP mocking library
These tools are powerful, and in this article, I am not explaining all possible uses; refer to corresponding docs for more details. Fire this one-liner to install everything you need:
npm i -D react-test-renderer @types/jasmine react-addons-test-utils enzyme enzyme-adapter-react-16 redux-mock-store nock
There are also some other tools that I had to install and configure to make everything smooth:
- @type/jasmine from above, so that Webstorm doesn’t complain about not seeing functions from Jasmine. You don’t have to import them; it’s just for the IDE.
- I had to install Watchman.
- In the root of my project, check mocks/popper.js.js. It’s a mock of the popper.js library I had to create, to get the
mountfunction of Enzyme working.
Jest searches for tests folders in your project tree and will run all files/tests from there. So for example – if I am testing reducers, I will create reducers/tests/my-reducer.test.js. I like this filename convention, but you can use anything else.
Testing Dumb Components
Let’s test our first presentational component. Check the src/components/tests/Toolbar.test.js* file and notice the imports, Enzyme configuration/adapter, and the 3 tests we have:
- Component actually renders – this is our first test, and we are using the
shallowfunction from Enzyme to render only the light/shallow
RcToolbarcomponent without any children. Enzyme has jQuery-like syntax, so it’s straightforward to find what you need and assert.
- Here I am checking that the Add button has the “+” icon from Material UI. I am using the
mountfunction here and not
shallowbecause I need to access a child component to assert.
- In the last test I am checking if toggle button/filter is turned on by default.
Another good example would be to test if the
RcList component renders N li elements when I pass in N countries.
So we are making minimalistic tests for presentational components – do they render and show what they need to show.
Now open src/reducers/tests/countries.test.js. The country reducer is a combined reducer for using normalized state, so we are testing both
list reducers here.
Testing reducers is really straightforward because they are pure functions. For the given input, we expect always to get the same output. In my example, we are checking if a country-add success will result in a state that contains the newly added country.
Notice that we are using the
addCountrySuccess action creator inside the test. That’s why we don’t have to test simple action creators explicitly. Refactor your code, so that you can use these action creators in both code and tests if you already don’t have them set up that way.
Testing Thunk Action Creators
It’s important to test thunk / async action creators to make sure that we have proper simple actions dispatched after a promise has been resolved. Open src/actions/tests/actions.test.js. The first thing you will notice is that we are using the redux-mock-store library to mock a Redux store. We are also adding thunk middleware to it. We are mocking an HTTP request (using the Nock library) since adding a country will result in a remote API call, and we don’t want that in the test. We are then merely dispatching a thunk action and, when done, checking if all expected actions have been dispatched. In this specific case, we expect that adding a country will result in a success event, closed dialog, and a message.
I don’t have any utility/common code in the project, but these should be fully tested, as they are usually pure functions.
It’s time to run our tests locally with
npm test. It’s configured to run only newly added/changed tests from the last commit (more details here). There is also interactive mode allowing you to press “a” to run all tests anyway. This doesn’t work well in Webstorm when
npm test is invoked from the UI, so just use the terminal if you want interactive mode.
I am using Travis, and if you check the travis configuration file, you will see that one new line has been added to enable CI testing. NPM testing will be invoked on every commit.
The testing topic is really complex, and there are many possible options, but I think this approach is relatively simple and tests the core of a React-Redux app – reducers, actions, and presentational components. In combination with a QA team, this can be a good testing strategy. 😉