Stubbing React Containers for Testing

Stubbing React Containers for Testing

  • 2016-08-27
  • 693

In many React apps, we follow the Container pattern with whatever container library we use, whether it’s Redux, React Komposer, Meteor or something else. We have a set of presentational UI components with some containers that wrap UI components with the data.

Sometimes, we could have UI components that use containers inside it. For example, take a look at the following component.

import React from 'react';
import { CommentListContainer } from './containers';

export const Post = (post) => (
  <div style={style}>
    <h1>{post.title}</h1>
    <p>{post.text}</p>
    <hr />
    <h3>Comments</h3>
    <CommentListContainer postId={post.id} ></CommentListContainer>
  </div>
);

Here, we are using a CommentListContainer inside our Post component. Everything will work fine when we are building the app. However, we’ll face a lot of issues when we are testing our Post UI component. We’ve seen cases like this when developers build their UIs with React Storybook.

Basically, with React Storybook, you test your UI components in an isolated environment from the app. In that case, the CommentListContainer doesn’t have the correct context to fetch the data it needs. In the end, it is impossible to test and use the Post component with React Storybook.

This is very common with Meteor apps.

Solution

Stubbing data or the container is the solution to this problem. Basically, we provide a dummy component that replaces the container. In this case, we can replace the CommentListContainer.

But how we can do that?

There’s no direct way to do this with React. However, this can be implemented in the container composition. Here, we are using a tool called react-stubber.

Then, we need to wrap our Container like this:

import React from 'react';
// compose if our container building library.
import compose from './compose';
import { mayBeStubbed } from 'react-stubber';

export const CommentList = () => (
  <ul>

  </ul>
);
CommentList.displayName = 'CommentList';

// Here we wrap our container with mayBeStubbed
export const CommentListContainer = mayBeStubbed(compose(CommentList));

That’s the only change you need to do on the app level. This won’t affect your app in anyway. It only works when the stubbing mode is activated.

Activate the stubbing environment

At the beginning of the test code or in the React Storybook config, you can apply the following code.

import { setStubbingMode } from 'react-stubber';
setStubbingMode(true);

With that, we tell the react-stubber to stub containers instead of applying the container logic. Then, we get something like this:

aspectRatioPlaceholder is-locked

Basically, it replaces the container with a div containing the displayName. Now, we can simply test our UI component Header without any issues.

Here’s the source code.

Inject a Stub Component

Sometimes it would be great if we could inject a component where our container is going to render. In those situations, we can do so like this:

import { stub } from 'react-stubber';

stub(CommentListContainer, (props) => (
  <div>Comments for postId: {props.postId}</div>
));

We need to do this before using the Post component. 
(The test file or the storybook’s config file is a good place for this).

Now, instead of the Container, react-stubber will render the component we’ve provided.

aspectRatioPlaceholder is-locked

source code.

Like this, you can do this for any other containers you like.

Implementation

This is very simple. In the app, the mayBeStubbed function does nothing; it simply returns the Component it receives. Once we activate the stubbing environment, it’ll simply return a Stubbed Component that shows the displayName.

We’ve also added a way to have our own component rendered instead of the displayName.

This is a very simple implementation that uses less than 30 lines and that can be used with any React Component. It can also be used with Redux, React Komposer, Meteor or anything you like.

React Komposer has a much deeper integration to stubbing based on this concept.

Suggest

Zero to Hero with React.js

Advanced React and Redux

Build Web Apps with React JS and Flux

Build Apps with React Native

Meteor and React for Realtime Apps