Test a Render Prop!

aka the missing paragraphs of “Use a Render Prop!”

Update: This article goes into detail of how to test render props in Enzyme. I’ve since moved away from Enzyme as I believe react-testing-library uses a far better approach! Try writing some tests with it and you’ll appreciate it! It allows me to refactor my code without touching the test suite and finds errors when I mess up. Something I always wanted, but never really got out of Enzyme. Good places to get started with react-testing-library are the react-testing-library-course repository and this talk. However, the following article shows how to test render props in Enzyme with ease and is still relevant for those who would rather continue using Enzyme (or for those who are stuck with it for now)!


One of the most transformative posts for the React Ecosystem last year was Use a Render Prop! by Michael Jackson. Michael argues that render props are ”a far more powerful pattern than HOCs” as they are more dynamic, allow for better composition and avoid name space clashes. After that post, the community has widely adopted render props. Even the React core team themselves integrated render props into the new Context API. Render props are great all around… except for one thing: It’s incredibly inconvenient to test components using them.

One of my coworkers at the time, Philipp Sporrer, noted this on the Michael’s article in this comment:

When writing unit tests for the `App` component you have to first shallow render the `App` component, then `find()` the Mouse component and shallow render the `render` prop of the `Mouse` component in order to make assertions on anything inside the `Mouse`’s render prop. With HoCs testing your component is as easy as passing props to it. […] Especially the one about testing bothers me a lot and I would love to have a simple solution for this.

The Problem

Let’s look at Michael’s final code from his article:

The slightly adapted final code of Michael’s original post containing the components we want to test

First we need to clarify the problem. There are two components here that can be tested: <App> and <Mouse>. Testing a component like <Mouse>, which has a render prop, is quite trivial. We only need to mock the render prop:

This article assumes a setup with jest and enzyme.

The real problem occurs when testing a component like <App>. <App> passes a function into the render prop of <Mouse> like this: <Mouse render={fn} />. How would we test this passed-in fn? It is part of the tests of <App>, but we need to mock the arguments <Mouse> calls it with.

We ultimately want to verify that the string “The mouse position is ({x}, {y})” gets rendered by App. Let’s try a naive approach first:

This does not work and fails with:

Expected value to equal:
"The mouse position is (0, 0)"
Received:
"<Mouse />"

That happens because the shallow rendering stops at the <Mouse> component. It does not dive further down the tree, as it is shallow rendering after all. The wrapper only contains:

<div style={{...}}>
<Mouse render={[Function: render]} />
</div>

One common solution is to instantiate another wrapper for the inner component:

This works nicely. We create a new wrapper by “expanding” the render prop of the <Mouse> component. In our case this is not a lot of code as we only go one level deep and the components are small. This gets problematic when we need to dive through multiple render props in tests though, as each need their own shallow wrapper.

The solution

What if all we needed to do was this

Now the wrapper variable holds a shallow wrapper containing only<h1>The mouse position is (0, 0)</h1>. We are able to call any prop as a render prop and to pass arbitrary arguments to it.

In detail: The renderProp(propName, ...args) function operates on the wrapper returned by the shallow().find() call, which holds the Mouse component. It calls the prop named propName of Mouse and passes the remaining arguments to the render prop. The return value gets wrapped in another shallow wrapper by the renderProp function, which is then returned. This eases the orchestration we had to do before.

An extension called @commercetools/enzyme-extensions provides the renderProp function. That package was written by my colleague Tobias Deekens and adapted by yours truly after we had some discussions around it. The package is immensely useful when testing components using shallow rendering, which rely on other components that are built with render props.

The renderProp extension works with any render prop. We can also invoke a children render prop which takes multiple arguments like so:

shallow(<App />)
.find(Mouse)
.renderProp('children', x, y)

This translates roughly to

shallow(
shallow(<App />)
.find(Mouse)
.prop('children')(x, y)
)

The renderProp function actually does a bit more to make working with the returned wrapper more convenient, but that is not important for the remainder of this article. @commercetools/enzyme-extensions also exports a low-level drill function which can be used in case you need more control.

Multiple Components

The solution really shines when multiple components using render props are involved. Assuming the <App> uses another component called <MouseDown> to keep track of the mouse button state:

Traditionally, we would have to introduce yet another intermediate wrapper:

This gets easier by chaining two renderProp calls:

Where renderProp is useful

As render props have become more popular, many libraries adopted them. For example, the renderProp utility is great when testing components renderingRoute components from react-router, or Query components from react-apollo.

This article showed how @commercetools/enzyme-extensions helps ease the biggest pain one experiences when testing render props. Hopefully the renderProp function comes in as handy for you, as it did for us. It may look like pure syntax sugar at first, but it really simplified our tests. The package also includes another nifty feature called until(selector), which keeps on shallowly diving down a component tree until an element matching the selector appears. Check out the README which includes setup instructions and more examples.


This article was written by me, Dominik Ferber. You can follow my stuff on Twitter where I chipper about Web Development as @dferber90. In case you’re ready for the decentralised web, you can find me at dat://www.dferber.de/, if not you can find me on the legacy web at https://www.dferber.de :P

I work as a freelance React consultant and I’m ready for a new project from Jan 2019 onwards. Get in touch on Twitter if you need a helping hand! In case you’re a developer and looking for a permanent gig: my last customer, commercetools, is awesome. They’re looking for React-y people in Berlin and Munich, apply here!