Christian BouvierMay 4, 2018

5 Tips to make your React/Redux app more Testable/Debuggable

In Octobot we ❤️ React and Redux for many reasons, but one of the most important for us is its ease of testability. As developers, we MUST debug and test our code to guarantee that it is working as expected throughout time, but debugging/testing is not always a pleasant task… the following tips can make it easier (hope so)

Intro: React/Redux apps

In a React/Redux app there will be a set of React Components connected to a single source of truth, the Redux Store, which is updated by the effect of actions generated by user interactions and events. Before an action reaches the Reducers, a pipeline of Middlewares intersects them and decides if they are able to go ahead or not, if other actions should be dispatched or even if the action should be modified. Once a Reducer was reached, it creates a new state (based on the existent one and the action received) and the subscripted components are notified to be re-rendered. The image below illustrates such data flow.

React/Redux data flow

So, lets see what can be done in order to improve code testability and debuggability.

TIP 0. Env: Setup your environment

Firstly, you have to setup an appropriate environment to work and debug your apps.

We highly recommend the following tools:

  • React DevTools: A great extension created by the Facebook’s team, that allows inspection of React component hierarchy in the Chrome and Firefox Developer Tools.

  • Redux DevTools: Another extension with amazing features. Lets you inspect every state and action payload, go back in time by “cancelling” actions, etc.

  • ESLinst A pluggable linting utility for JavaScript with a big set of rules and good practices. We use it with Atom and it is very easy to integrate it.

TIP 1. Redux: Action Creators as Pure Functions

Many Redux developers (perhaps you too) are using Async Action Creators (with libraries like Redux-thunk), but the fact that an action creator is async, makes the app much more complex and doesn’t take advantage of two important players: pure functions and Middlewares.

Let’s suppose we have the following async action creator, which uses axios and is responsible of getting a list of FAQs. If the request succeeds, it dispatch FETCH_FAQS_SUCCESS. If it fails, it dispatch FETCH_FAQS_ERROR.

const fetchFaqList = (dispatch) =>
return axios.get('/faqs')
  .then(function (response) {
    dispatch(FETCH_FAQS_SUCCESS(response);
  })
  .catch(function (error) {
    dispatch(FETCH_FAQS_ERROR(error);
  });

There is nothing bad with this code, but..

  • Every action creator needs to handle errors and set headers (repeated code)
  • Testing gets harder as more async Action Creators are added.
  • The server communication strategy is embedded in multiple parts of the code, making it difficult to change it in the future.

So, you should be asking to yourself.. “Good.. but how can I avoid Async Action Creators? How will I connect with my API?”

That is the point, a smarter and more redux-compliant solution is to take advantage of Middlewares, and replace Async Action Creators for an API Middleware which will be responsible for all the async tasks of your app. This way, the fetchFaqList could become a dead-easy-to-test pure function.

To achieve this, we need to move all the information in the action creator to the action. A good approach is to use Flux Standard Actions (FSA) and define a unique API action who carry the request data and what must be dispatched if the request succeeds or fails.

Let say, something like this:

const fetchFaqList = () => ({
 type: API_REQUEST, // unique type for all the async actions
 payload: {
   url: FETCH_FAQS_URL,
   method: 'get',
   success: FETCH_FAQS_SUCCESS,
   error: FETCH_FAQS_ERROR,
 }});

Then is up to the API Middleware to cope with it. And this is what we will discuss in the next tip.

TIP 2 Redux. API Middleware

To make easier to understand, debug, and test your code you should keep it as clean, short and stateless as possible. Keeping the Action Creators “clean” from async code means moving it into another part of the stack: Middlewares.

So, we need to create a generic piece of code, which should be able to serve any API request and requires only the information passed to it in an action. One way to do this, is to define a new action type that is unique to API-related events (API_REQUEST).

That way, our Middleware could look like:

const apiMiddleware = ({ dispatch }) => next => action => {
if (action.type !== API_REQUEST) {
  // Lets action continue through the Middlewares pipeline
  return next(action);
}
// Perform Async code
};

With this approach, there will be only one piece of code with “async powers”, and it would be easy to test and maintain the whole application.

If the action received by the API Middleware is not an API_REQUEST, then lets it flow through the Middlewares pipeline. If it is an API_REQUEST, parse the information in the action’s payload and performs the async task.

const apiMiddleware = ({ dispatch }) => next => action => {
    if (action.type !== API_REQUEST) {
      return next(action);
    }
    axios.get(action.payload.url)
    .then(function (response) {
      dispatch(action.payload.success, response);
    })
    .catch(function (error) {
      dispatch(action.payload.error, error);
    });
  };
}

In fact, is quite similar to what is behind the scenes when you use Redux-thunk or other similar library.

TIP 3 React. Stateless Components

As you probably just know, since React 14 you can use Stateless components. A stateless component is a simpler way to define components using plain Javascript functions. This approach offers many benefits:

Stateless Component vs Class Component

Easier to understand

A stateless component doesn’t need: the class keyword, extends react.component, implements the render method, etc.. It is just a function which receives props and spits out JSX.

On the other hand, if you destructure your props in ES6 (just like the example above ({ children }), then all the data you use is now specified as a simple function argument. That is great for comprehension, readability, type checking and code completion.

Limited to no-behavioural components

Yes! Stateless components are useful for Presentational purposes only, and that enforces you to maintain logic away from presentation. That way, logic needs to be handled in other components (containers) or in the redux state.

Easier to test

Since Stateless components are pure functions, the test assertions are very straightforward. You provide values for props, and receive some markup. No mocking, state manipulation, special libraries, or tricky test are needed.

TIP 4 React. PropTypes

As you should know, Javascript is a weakly typed programming language. It means that you don’t need to declare the type of a variable, and also that a value of one type can be treated as another. It is a good thing, but sometimes it can lead to some difficulties.

On the other hand, Props are the mechanism React uses to let components communicate with each other, particularly, from parents to children components. That way, a component will have an object props which contains all the properties passed to it.

In a world of reusable components, where different developers can use them in different times of a project, it is desirable to have a way to control and declare what are the properties a component support.

This is where React’s propTypes play its role. It’s essentially a dictionary where you define what props your component needs and what types they should be.

Among others, the benefits of using PropTypes are:

Wrong type detection

It helps you easily catch bugs caused by passing data in the wrong data type. An error message will be printed in the developer console of your browser. Just like the one bellow.

PropType error screenshot

Readability

All the available props including their desired data type can be seen at one place.

PropTypes Readability

Code Completion

Some code editors support code completion for props, so you can see the available props while typing in the component in a tooltip.

Conclusion

And, that’s it! We hope these tips turn out useful for you in some way.

Both React and Redux are amazing tools, and its combination is really powerful! But (we know from uncle Ben that..) with great power comes great responsibility, and a well tested app is one of ours responsibilities as developers, and anything that helps is always welcomed.

If you think we are missing something important, make sure you mention it in the comments below!

Thanks for reading! 👋