TDD and React Components

As I started a new project recently, I’ve been trying to up my test driven development game. I wanted to go through my approach to TDD for a single stateless React component. If you follow along with this blog you’ll be building a stateless component that controls a single radio group. This will not blow your mind! :)

Setup

If you are just looking for some example tests, the code is at nackjicholson/stateless-radio on github.

There a lot of other resources out there about getting a testing workflow setup with React/ES2015. This isn’t one of them, so if you need a little help getting there, check these out.

  1. Eric Elliot tdd-es6-react
  2. Tero Parviainen Full Stack Redux tutorial
  3. My yeoman generator for react components generator-react-nack.

The rest of this blog is going to be about diving deep on the process of TDD for react components.

Test Description Driven Development

When I begin to TDD, the first thing I do is write a new test file and attempt to write english sentences which describe the thing I’m about to build. These descriptions are some of the hardest work I do in the process of TDD. Often expressing functionality in sentences forces me to deeply think though what I’m about to do. If I’m not sure if something is necessary I’ll do what I call a “spike”. That is, I write some code quick and dirty to verify any theories I have and do some general research on the problem. Spiking is a huge part of TDD for me, especially while I’m still learning the basics of the tools I’m using.

Take a moment to read these descriptions. They describe a component which takes some props and renders some HTML which looks like this:

It also responds to the user selecting one of the radio buttons by executing a callback prop onSelection.

This is a pretty standard radio button group, in fact I took the technique directly from the MDN documentation How to structure an HTML form.

Write a Test

The first test says that our component module should return a function. I thought we were making a React component, shouldn’t it return a class or a JSX object? I’ve taken the approach of making my shared components accessible via a factory function. There is an excellent explanation of this technique in an @_ericelliot project called Pure Component Starter. The idea is to share a single React instance across an entire app, and let your components have an injected rather than global dependency on React.

This is a very simple test, and the comments showcase what I’m shooting for in all my tests. This is another approach taken from an @_ericelliot blog 5 Questions Every Unit Test Must Answer. Also, I take some queues from an excellent talk by @searles — How to stop hating your tests.

This test fails because I don’t even have a module named statelessRadio.js yet. Now we write it, it’s a function that returns a function, and we’re on our way!

Testing React Rendering with a No-Magic approach

For testing react components I’ve been using a module named teaspoon, and I highly recommend it. After experimenting heavily with the react-addons-test-utils, cheerio and enzyme I’ve come to the conclusion that almost all my tests end up looking better when using teaspoon. The react-addons-test-utils make tests very verbose and almost unreadable. Cheerio is excellent but won’t help you with shallow rendering or any lifecycle react testing. Airbnb’s enzyme has a lot of promise, and solves most of the same things teaspoon does, but at this time I’ve run into many dead ends especially when testing nested components, or simulating events. I have some hope that because it is an Airbnb project it will become better with time.

These first two rendering tests are fairly similar. They assert that the basic parts of the statelessRadio component are present. We are going to give our component some props, baseId and titleText, and we expect to see a <div> with that id, and a <p> with the title text

Running these tests is going to throw a fit. Telling you that nothing worked
and something about inst.render. This is actually a good thing, get used to failure, this is the essence of TDD. It is pretty obvious this is just react complaining about our stateless component not returning any elements. To make a more sensible failure, make the component return a <div></div>.

And carrying that a little further, get them all green. No problem, these are quite easy to pass with some basic JSX.

Assertions against the rendered DOM

The next pending test “should render radio inputs and their labels from the inputs prop” means that we need to provide our component with a inputs prop, and we should expect to receive <input> and <label> HTML elements that correspond to the data in the input prop. This test is a little more complex and involves using teaspoon to extract information from the rendered DOM on which to make assertions.

We want to provide props like this:

And verify that our DOM contains something like this:

And here it is:

We have quite a bit of repetition in these tests, but it’s often more important to be isolated and simplistic than it is to be DRY. Complicated test setup is a bane for any large project, and a constant source of pain when refactoring. Take the “No Magic!” approach whenever possible. This means avoid things like before/after setup, big fixture objects, and fancy assertion libraries (chai), because they give you the power to make your tests more complex. I’ve opened up test suites (my own included) where you would think having before and after setup was a required feature, because it’s just omnipresent whether it needs to be there or not. Often it’s just clutter which makes the test more difficult to maintain and much more difficult to reason about.

I don’t mean you shouldn’t help yourself with pure functions occasionally. It can be really helpful for readability and maintainability to do so, just give it some thought beforehand, and don’t be afraid to dial back your normal DRY instincts a little bit when writing unit tests. Right now our tests have a shared prop baseId and they generally make their own additional props. Below is a createComponent helper that provides a way for each test to “extend” in it’s own isolated prop setup, without quite so much boilerplate.

This benefit of boilerplate reduction is more apparent in components that have many required props that need to be present in each and every test. In this case we’re really only saving ourself from typing and reading `baseId` over and over again.

Now that we have our failing inputs rendering test, let’s try to get it passing.

We will use the props.inputs collection to create <input> and <label> elements using Array.prototype.map.

This code which we thought was going to pass, breaks some of our tests which were previously working. This kind of thing happens quite often when doing TDD, because you just can’t possibly think of everything beforehand, this is a natural (albeit frustrating) part of TDD. You’ll discover that you missed something in your implementation, or have to reconsider your design, or write new test cases to cover the unexpected break. For us, it’s pretty clear what’s happening. When inputs is being mapped over in order to create components, it is undefined in those two broken tests. We can either add inputs to our default props as an empty [] inside the createComponent function, which is basically making that a required prop for our component. Or we can make our component default the inputs collection to an empty array if the user didn’t provide it. One of the ways to accomplish this is to use destructuring and default assignment with es6 function parameters.

Tests pass, with warnings.

Warning: Each child in an array or iterator should have a unique “key” prop. Check the render method of `StatelessRadio`. See https://fb.me/react-warning-keys for more information.
Warning: Failed form propType: You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultValue`. Otherwise, set either `onChange` or `readOnly`. Check the render method of `StatelessRadio`.

I’ll usually ignore warnings like this until my tests are all green and complete. Then I’ll take a refactoring pass at the end in order to eliminate warnings, lint, and clarify code.

Get to know your HTML element API

You will find some assertions are easier done using teaspoon’s .dom() method to return you the normal HTML element and then access the node’s js properties. This statelessRadio component we’re building doesn’t require us to use .dom(), but using .get() to transform our teaspoon collection into array of elements gives us the power to use normal JavaScript to work with the rendered DOM.

Passing this test is done by comparing the defaultValue prop which is sent in, with each inputItem.value and then setting the defualtChecked property of the <input> element.

Event Simulation and Callback Props

For dumb components in our React/Redux system all they should really do is render data as html, and respond to user action. The above cases handle a large majority of techniques that I’ve employed when testing react component rendering, and now we’ll look at an example of how to test interaction. The StatelessRadio component should take an onSelection callback prop which it calls with the value of the selected radio input. To write this test we can use sinon.spy to spy on the onSelection prop, and teaspoon to simulate the change event on an <input>.

Although it looks a little different, we’re still sticking with that consistent spacing and variable naming in our Arrange, Act, Assert layout.

Since we want to only send the event.target.value (the value of the selected input) to our callback and not the whole event, we’ll make a function to respond to the change event which executes onSelection(event.target.value).
Note I put the default argument () => {} to stand in for the user if they don’t provide a callback function.

Clean up

You could be done with this code, it works as defined and isn’t too horrible. But we still have that annoying warning in the test output, we’re failing any sensible linter, and it just feels a little bit cluttered. After a quick refactor here is what I ended up with for the final statelessRadio component.

Conclusion

Writing this blog, and being more mindful of my TDD methodology has taught me that TDD and Programming aren’t so much a skill as they are a discipline. Having the discipline to really write tests before your code will force you to think about that code many times over before you ever write a line of it. If you’re like me you might want to start with some “spike” code, draw out ideas on a whiteboard, and concentrate on the english sentences which can describe your code. I find all of these exercises useful in increasing my knowledge of the code, the architecture, and the tools we use. Writing tests first implies necessity for a deep understanding of React, it’s lifecycle, the HTML elements it creates, and the HTML API’s that javascript provides for you. I had massive amounts of trouble for about a week when writing tests first, because at every stage of the process I had to go read pages of React and MDN docs in order to write a sensible test. That’s not a bad thing at all!

Good luck, and thanks for reading!

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.