This post is also available as a youtube video that can be checked out here:
The video version covers the topic with more depth than this article, and I highly recommend you check it out
Redux
Redux is undeniably one of the most well known and widely used state management libraries available. There was a time it was nearly impossible to hear the keywords react or react native with an "and redux" append at the end.
Today, even though redux is far from its monopoly days, it still is a super relevant tool, and a common doubt many people have is how to test redux applications.
Many times, I've heard the question
"What changes when testing a react (native) + redux application ?"
The answer to that is, almost nothing.
In this tutorial, we'll be using react native testing library and the main philosophy of this library is focusing on testing how your application works, instead of its implementation.
A major positive point of this testing strategy, is only caring about what's important, the software's functionality. So, if you restructure your entire codebase, but everything still works as before, your tests won't break.
But, even though the tests themselves don't change, the way you render your tests must change to accommodate redux needs.
Usually, when testing a component we only render it, like the following example
import { render } from '@testing-library/react-native';
import Component from 'component'
describe('When testing a component', () => {
it('Usually only render the component itself', () => {
render(<Component />);
})
})
But, if you are familiar with redux, you know that every component which uses redux must be wrapped inside a provider with a valid store.
If we try to render a component which relies on redux for its state management without the expected provider, we the following error will be thrown
could not find react-redux context value; please ensure the component is wrapped in a <Provider>
The way to solve this, is by simply wrapping your rendered component in a provider, like the following
import { render } from '@testing-library/react-native';
import Component from 'component'
import { Provider } from 'react-redux';
import yourStore from './store';
describe('When testing a redux component', () => {
it('Should be wrapped by a provider', () => {
render(
<Provider store={yourStore}>
<Component />
</Provider>
);
})
})
But, doing this every time will pollute our code base, and there must be a better way to handle this rendering, don't you agree ?
Thankfully for us, we can leverage the existence o jest globals
If you are not familiar with jest globals, or jest itself, here goes a quick explanation:
Jest - is a test runner shipped by default in react native projects
globals - globals are globally available helper functions, usable in our test suite without the need to import them
Although a jest global for rendering redux applications does not exist, we can easily create new globals with jest.
So let's create a global called renderWithRedux, which will encapsulate this logic for us
Creating globals
To create a redux global, the first thing we'll need is editing our package JSON to expect a setupFile
Search your the following line in your file
"jest": {
"preset": "react-native"
}
and edit it to look like this
"jest": {
"preset": "react-native",
"setupFiles": ["<rootDir>/setupTests.js"]
}
Basically what we are doing here is telling our application to read a setup file which resides in our root directory, hence the rootDir tag, and is called setupTests.js
But, this file does not exist yet, so, create it, and leave it blank for now.
Creating our rendering function
First start by creating a folder called helpers, and inside of it, create another folder, called testHelpers. Inside the testHelpers folder, create a file called renderWithRedux.js
Inside that file, let's create our helper.
it will look something like this
import { configureStore } from '@reduxjs/toolkit';
import reducers from '<pathToYourReducers>'; // this is not a real path
import { Provider } from 'react-redux';
import { render } from '@testing-library/react-native';
export function renderWithRedux(renderedComponent){
const store = configureStore({
reducer: {
...reducers
},
});
return render(<Provider store={store}>{renderedComponent}</Provider>)
}
In this example I use redux toolkit to create my store, but you can use whichever method you like, as long as it generates a valid store at the end
the renderWithRedux function expects renderedComponent and then renders it wrapped in a provider, so you won't have to manually do this every time
at the end, the result of our render is returned
a store is created every time to ensure a clean test state. This is important to ensure each test is running in isolation and hence when you add a new test, you don't risk breaking pre-existing tests, which was a possibility if we didn't take this measure.
Exposing our function globally
With our function created, all we have left to do before we're able to use this function in our codebase is make it globally available in our setup function.
Open our prior created setupTests.js file, and populate it with the following code:
import { renderWithRedux } from "<pathToTestHelpers>/renderWithRedux"; //Not a real file
global.renderWithRedux = renderWithRedux;
in the following piece of code:
global.renderWithRedux = renderWithRedux;
you are extending the global object, and adding a new property called renderWithRedux to it, with the value of our renderWithRedux function.
From this point on we can use this function to render components which rely on redux, so, doing the following:
import ComponentWithReduxData from 'componentWithReduxData'
describe('When rendering a component that uses redux data with the renderWithRedux global', () => {
it('Should render correctly', () => {
renderWithRedux(<ComponentWithReduxData />);
})
})
will be a valid operation, and should mitigate any error related to redux rendering in our application
renderWithRedux returns the exactly same query methods as testing library's render method
There it is, now you can your redux functions using a global function instead of redux boilerplate
Top comments (0)