Testing APIs with React and Redux. Part one – testing in a React app
In the world of software development, unit testing and test-driven development (TDD) are widely advocated for improving product quality and confidence in maintenance, and reducing the overall number of bugs and defects.
In an ideal situation such tests would run far and wide across each software product to cover every single line of code. However, in the real world this would not be an optimal way to spend development effort or to meet project deadlines. Instead it makes a lot more sense for good quality testing to focus on the most complicated areas of software to improve resilience against failure.
In a modern web application the system complexity is likely to be spread across several layers. For example this could be a single-page application (SPA) with a rich client-side (front-end) framework connected to a database via a server-side (back-end) API. Or it could be a combination of connected services in the form of a microservices architecture. A good testing strategy should therefore no longer be about considering each part of the system in isolation, but about testing the chain together to verify integrity across layers.
The boundaries that exist between such web application layers are typically API calls. These can be a fragile part of the process, liable to change frequently during development and with subsequent maintenance. Not only that, but it is common for different developers to work on separate parts of the system, and consequently make unintentional changes that break the validity of the boundary in some way.
Writing tests to cover these boundaries can reduce overall risk for a project, and increase both developer and customer confidence that future changes won’t break the system. In this article, and in a second part to be published next month, I’m going to take a detailed look at testing API calls.
Testing in React
React (and its mobile equivalent React Native) is an excellent front-end framework for developing modern applications that we have recently become familiar with at Red River. An open source project by Facebook, it has a strong community of developers, and in combination with Redux for managing application state it allows increasingly complex applications to be developed rapidly.
A second option is to use the testing framework Jest, which has been developed and is used by Facebook, and is a complete library containing everything needed to write tests for both React and React Native. It is specifically designed to be easy to set up, fits in tightly with the React philosophy, and has some nice features such as snapshot testing for detecting changes in rendered component output.
Some developers have shunned Jest in recent times, largely because of its slower performance and built-in automatic mocking – which can lead to unexpected results. However, since version 15.0 of Jest, auto-mocking is disabled by default, and recently there have been significant improvements in performance.
These advances mean that using Jest should be considered a great starting point for testing in React applications. It’s the framework I’m going to use to demonstrate testing API calls in this article.
Mocking an API call
A classic method of testing APIs is to mock out any asynchronous API calls so the remaining functionality of the application can be tested (although as we’ll see later, this does not provide full integration testing coverage of the system).
Getting setup with Jest is relatively straightforward. For new projects there are many React templates that come bundled with it already (for example create-react-app). For existing projects it can simply be installed using npm install jest –save-dev or yarn add jest –dev commands. If using webpack the process is a little more complicated (see using with webpack).
A basic API call may be implemented using the fetch() command, and wrapped in a self-contained function. Here we parse an expected JSON response and return the promise to let our calling code handle errors.
Writing a test for our API call is relatively straightforward. In the example below we are importing the fetch-mock library, which allows http requests using fetch() to be overridden. First we initialise a test data object for the request, then instruct fetch-mock to return it as a fake response when any subsequent HTTP GET calls are made.
Our API call is then executed, and we use the concept of promises to check that the API response matches our initial test data. Finally we tell fetch-mock to restore the fetch() command and return our API promise – this ensures Jest handles the asynchronous nature of the test correctly.
Our tests themselves go into a separate file ending with a test.js extension. With Jest correctly configured, running and verifying the tests is usually a case of running npm test or yarn test. Alternatively, editor plug-ins such as the Jest add-on for Visual Studio Code run the tests automatically as they are written, and even show up where test assertions are failing.
Testing Redux Integration
In a React application using Redux for state management, it is common to wrap an API call within a Redux action (using Redux-Thunk middleware to deal with the asynchronous aspects). The upshot of this is that the state of the API call becomes part of the application state, and it is therefore easy for the UI to reason about this and update appropriately in response.
A nice way to wrap the API in a Redux action is for the action itself to dispatch further actions when the API call has started, when it is successful or if an error occurs. Each of these in turn can be handled by a Redux reducer, and the state updated as necessary:
As the test API call is now executed from the test API action, we can update our tests to call the action instead, and check the appropriate sub-actions have been fired. In order to do this we need a Redux store for our tests, to dispatch our actions against, and a way to check what has been executed.
That’s where the npm package ‘redux-mock-store’ comes in, which simulates the Redux store for the purpose of testing asynchronous actions in exactly that way. After installing using the appropriate npm install redux-mock-store –save-dev or yarn add redux-mock-store –dev commands, it can be imported before our tests and configured as follows:
Our previous API test can now be modified so that we first create a test instance of the configured mock store (using an initial state for the store if necessary). We can then use the mock store to dispatch our action and wait for it to complete.
The store implementation in redux-mock-store has a useful getActions() method, that returns an array of actions that were dispatched from within our main testApiAction() – in this case our sub-actions. It is then possible to use this to check the flow of the API call action, and ultimately that the ‘success’ action was generated with the correct mocked fetch response data:
Improved API testing
In the examples above we have mocked out our API calls and therefore the boundaries of the application. This is a great starting point for testing APIs, allowing us to test the functionality of the individual system right up to the boundary, and verify its integrity assuming all other layers in the system are coherent.
However, our tests in their current state don’t allow us to detect breaking changes to code in the API service being called. In the second part of this article we’ll look at how this can be achieved, in order to successfully test integration across the layers of a system.