in JavaScript

React Dependency Injection, write easier unit tests

– Me: Do you create presentational and container components?
– You: Yes.
– Me: Awesome! And are you writing unit tests for your containers?
– You: No…
– Me: Not good! What’s the problem?

The problem is higher order components are great to add functionality to a component, but they make it difficult to test the components they enhance.

If you don’t have time to understand the problem and you want see the solution straight away, then click here.

Let’s draw the problem. This is your component:

redix-post-venn1 copy 2
Now we are going to create a container component, it’ll wrap your component into another component. The idea is to separate the logic from the presentation. So the outer (blue) circle will be responsible for getting the data the inner (green) circle needs to render the “visual” stuff.
redix-post-venn2

So far so good. We’ve got a nice separation of concerns – logic on one side, and presentation on the other. But we added a small but important change. If I instantiate the container(outer component), I will instantiate the inner(presentational) component. Maybe at first it doesn’t look like a big deal, but:

  1. I want to write a unit test for the container. If I need to test for instance that when the container is mounted it fetches data, when I mount the container it will also instantiate the presentational component and all the children components. What if my children have other containers fetching more data, do I need to execute that? Should I mock them? Do I need to know the implementation of the children of my children? I should be able to mount my container without mounting everything below the tree.
  2.  I want to test that my container component injects the right props to the presentational component. But when I instantiate the container I don’t have access to the presentational component, what I get is a pointer to the outer component. Let’s see an example:
import { mount } from 'enzyme';
import PhotosContainer  from '../../src/containers/PhotosContainer';
const container = mount(
  <PhotosContainer />
);

In the example the const container points to an instance of PhotosContainer. It means that:

  1. If PhotosContainer has a method called paginateNext() I can call it from container.paginateNext(). Nice.
  2. If I want to see if paginateNext worked I need to check what was rendered in the presentational component. So I need to mount it, render all the children down in the tree and check what was rendered (therefore I need to know details of the implementation of the presentational component, for instance: component.find('div.photo-class')). Not good.

Things can get more complicated when we start wrapping our container with other components (which is very common). Imagine that you want to connect your container to Redux, it could look something like this (the example is oversimplified):

class PhotoListContainer extends React.Component {
  constructor(props) {
    this.fetchPhotos = this.fetchPhotos.bind(this);
    this.paginateNext = this.paginateNext.bind(this);
    this.state = { page: 1 }
  }
  componentDidMount() {
    this.fetchPhotos(this.state.page);
  }
  paginateNext() {
    const page = this.state.page + 1;
    this.setState({ page });
    this.fetchPhotos(page)
  }
  render() {
    return <PhotoList photos={this.props.photos}/>
  }
}
export default connect(
  (state) => ({ photos: state.photos }),
  { fetchPhotos: actions.getPhotos }
)(PhotoListContainer)

Let’s draw it:

redix-post-venn3 connect

Do you see the problem? Let’s see the test again:

import { mount } from 'enzyme';
import PhotosContainer  from '../../src/containers/PhotosContainer';
const container = mount(
  <PhotosContainer />
);

In the example above now the const container points to an instance of connect. It means that:

A) I cannot call paginateNext() from container.paginateNext() to test the method. Not good.

B) If I want to see if paginateNext worked I need to check what was rendered in the presentational component. So again I need to mount it, render all the children down in the tree, and check what they rendered (yes, my unit test needs to know implementation details of the presentational component that are not the container’s concern). Not good.

Let’s recap the problem

I have a container component called PhotoListContainer that does two things:

  1. It calls a function that fetches photos (which is actually action.fetchPhotos).
  2. It passes an array of photos to the presentational component using a prop called “photos.”

I want to write a unit test for those two things but:

  1. I cannot instantiate the PhotoListContainer and get a reference to it to call its method fetchPhotos.
  2. I cannot check if the container’s fetchPhotos function works without rendering the children components. My test needs to know some implementation details of the children that are not concern of the container’s unit test; for instance: component.find('div.photo-class')

We can find ways to go through the outer components (connect in the example, like {withRef: true}) to get an instance of the container, but it’s not a good idea because our container can be wrapped with many higher order components. That means that we’ll have to update our test for every new higher order component that wraps the container, because we are keeping a reference to these wrappers. There should be a better way, let’s change the approach.

onion2

The solution: Dependency Injection.

Instead of getting a reference to the outer component and then going in, we want to be actually in the centre. Then no matter how many layers our component is wrapped in, all we need (both functions and data) will be passed down as props. By “hooking” the props of the presentational component will get all we need.

The idea is simple. We don’t want to access the container’s instance. We just want to access: i) the data the container passes to the presentational component and ii) all the public functionality of the container. And both, data and public functionality, are set as props of the children component. And every higher order component (connect, injectIntl, withRouter, etc) does the same, they inject data and functions to the children through props.

What we are going to do is to inject two different things to our containers when we are testing. On one hand a different presentational component, on the other hand some of the functions that are injected via the higher order components because they may execute expensive operations like fetching data from an api.

Benefits of this Approach

By injecting a different presentational component we get the following improvements:

  1. We can execute the public methods of the container (because the reason they are created is to be injected to the children), and also execute the methods injected by the other higher order components that compose the container. All that just using the presentational component props. Example expect(injectedFunctions.fetchPhotos.called).to.be.true // scroll down to the end of the link
  2. We can access the data that the higher order component generates simply by accessing the props of the presentational component since the ones we care will be injected to the presentational component’s props. Example expect(test.props.photos).to.be.deep.equal(photos) // scroll down to the end of the link
  3. We can stub the render of the presentational component so nothing is rendered even if the container is mounted. Remember, we are writing a unit test for our container, the rendering is not the concern of the container. We improve performance in our tests, specially if the children of the presentational component contain other containers that execute expensive operations. And we hide implementation details of the container’s children.

By injecting different methods from the higher order components, like actions from the react-redux connect, we get the following improvements:

  1.  We can reduce the complexity of our unit tests.
  2.  We can improve the performance by stubbing expensive operations like api calls.

Examples

Dependency injection in the container of a React Redux project:
https://github.com/alexlbr/redix/tree/master/examples/react-redux

Dependency injection in the container of a React Redux project with actions in the context:
https://github.com/alexlbr/redix/tree/master/examples/react-redux-context-actions (react-redux proposal)

Further reading

Presentational and Container Components

Container Components

Mixins Are Dead. Long Live Composition