React + TDD = 💖

Adam Mausenbaum
15 min readMar 13, 2017

--

In this post, I want to show you three things:

  1. Writing tests in React is easy.
  2. Test driven development (writing tests before code) is easy too.
  3. This is an arbitrary third thing as we need to always have three things.

We’ll do this by creating an app from scratch and trying to write tests first. A quick disclaimer of what I will not do in this post:

  • I will not tell you why testing is good. You should know this by now.
  • I will not tell you what kind of tests you should be writing. Use common sense.
  • I will not tell you why TDD is TheBestThingEverInvented. I don’t care, I just find it works for me.
  • I will not rewrite something that has already been written. I will link to it instead.

TLDR: see the completed source code.

TDD in three words and a diagram

Red, green, refactor. Those are the only three words you need to know.

  • Red: write a test before you even start writing code. The test is obviously failing (red) as you haven’t written any code to make it pass.
  • Green: write the minimal amount of code to make this specific test pass (green).
  • Refactor: make that code better — you might have taken some shortcuts to get the test passing.

Write the next test, rinse, repeat. Without evangelizing why TDD is the best thing since big data, the net result of the above:

  • You’ve clearly understood the requirements as you’ve defined a test for each acceptance criteria. You might even think of some new ones that the product owner missed.
  • You’ve documented the code and requirements (yes, this is a valid form of documentation, and no, gherkin is not needed).
  • You’ve tested that your code does what it’s supposed to do.
  • In 16 weeks, when the next guy changes something that breaks your code, he’ll feel warm and fuzzy knowing that you helped him out and gave him the confidence to be a better man.

A quick word on tools

When writing and running tests, we generally need three (!!) things:

  1. A test runner: something that actually finds and executes your tests (and reports on the output).
  2. A test framework: some way of defining, organising and describing our tests.
  3. An assertion library: a set of convenience methods we can use for setting expectations of our tests and asserting whether those expectations are met (in me today)

In this blog post we will use Jest and Enzyme. Jest ticks all three of the boxes above and Enzyme provides us with some nice tools on top of React’s built in test utilities.

If you’re interested in exploring other tools you might want to look at Mocha, Jasmine, AVA or Tape. I’m sure there’s some comparison online on what tool does what and which one is the ultimate best for all time, ever.

Getting your app ready

The easiest way to bootstrap a simple React application is to use the Create React App scaffolding tool provided by Facebook:

npm install -g create-react-appcreate-react-app react-tdd
cd react-tdd
npm install enzyme react-addons-test-utils --save-dev
npm start

Then open http://localhost:3000/ and you’re ready to go! Let’s also boot up Jest:

npm test

The above command will run Jest in watch mode. It will continuously monitor your files and smartly run the correct tests as you save. You should be seeing something like this:

Jest test results

What are we building?

Before writing any tests, let’s quickly define what we’ll be building. We’re building a shoe store. Not much more to be said about that. However, let’s be fancy and do some backlog grooming (+10 points for awesomeness). Let’s first decompose these into user stories. As a user I should be able to:

  • View a list of products
  • Click a specific product to add it to the cart
  • View the number of items in my cart
  • Filter the list of products by brand

This concludes the role of our product owner. Our tech lead now takes a quick look at these stories and tells us that we should split our app into the following React components:

  • App: the app itself. This will also be responsible for all state.
  • ProductList: responsible for rendering the list of products. The list of products are passed in as props. The function to call when a product is selected is also passed in as props.
  • Filter: allows the user to enter some text. The function to call when text is entered is passed in as props.

Creating our first component

Let’s not try boil the ocean here — we have a very lengthy list of requirements. Let’s be smart, like Uncle Yash taught us, and just start with our first user story:

As a user I should be able to view a list of products

Before we rush into writing any tests, let’s quickly see what we have to test. We flip our story card over, and see that it has the following acceptance criteria:

  • The list of products should be displayed as an unordered list.
  • Each product listing should include the product name and brand.

Even though we’ll be writing our tests first, we still need at least some code that we can put under a test. Let’s create the folder and file /src/components/ProductList:

import React from 'react';function ProductList (props) {
return (
<div>ProductList</div>
)
}
export default ProductList;

Let’s also go ahead and include this new component in App.jsx and clean-up some of the other stuff we don’t need anymore in /src/App.js:

import React, { Component } from 'react';
import ProductList from './components/ProductList';
class App extends Component {
render() {
return (
<div>
<h1>My Product Store</h1>
<ProductList/>
</div>
);
}
}
export default App;

We can now finally start writing our first test for the ProductList component.

Typechecking is kinda testing too

In our first test we want to define and test the interface for ProductList. Basically we want to test that our component is always passed an array of products.

Despite Javascript and React not being strongly typed (go home TypeScript, nobody wants you here), we don’t have to manually write tests to check these interfaces as the React folks have kindly provided us with a gift: PropTypes.

PropTypes allow us to define what props must be passed in to our components. In this case we want to ensure that we are passed a prop called products all the time and that it is an array (of undetermined shape for now). We can add PropTypes to our ProductList.js component:

import React from 'React':// stuff from beforeProductList.propTypes = {
products: React.PropTypes.array.isRequired
};
export default ProductList;

Even though all our tests continue to pass, you will now see a very polite warning in your browser and in your test log:

Typechecking in React

The reason the warning is show up here is that our in App.test.js we are rendering the actual App component. If you take a look at our code for the App component now, you’ll notice that it’s rendering the ProductList component without passing in any props.

Let’s quickly fix this by adding in some fake products and passing them to the component. The file /src/App.js should now look like this:

import React, { Component } from 'react';
import ProductList from './components/ProductList';
class App extends Component {constructor(props) {
super(props);
this.state = {
products: [
{id: 1, name: 'AirMax 90', brand: 'Nike'},
{id: 2, name: 'Yeezy', brand: 'Adidas'},
{id: 3, name: 'Classic', brand: 'Reebok'},
]
}
}
render() {
return (
<div>
<h1>My Product Store</h1>
<ProductList products={this.state.products}/>
</div>
);
}
}
export default App;

We should now have no more warnings in our test console. My personal preference is to define the PropTypes for my components as early as possible. I find it’s a useful way of thinking through the component structure.

Testing what’s rendered

In our first real test, we want to assert that for every product we pass in as props, we render an unordered list item. More technically, for every item in props.product, there should be a corresponding number of <li> elements rendered.

The test itself

Let’s start by creating an empty test file /src/components/ProductList.test.jsand then adding the following code (don’t worry we’ll discuss it in detail shortly):

import React from 'react';
import {shallow} from 'enzyme';
import ProductList from './ProductList';
it('should render a list of products as an unordered list', () => {
const mockProducts = [
{id: 1, name: 'Mock Product 1', brand: 'MockBrandA'},
{id: 2, name: 'Mock Product 2', brand: 'MockBrandB'},
{id: 3, name: 'Mock Product 3', brand: 'MockBrandC'},
];
const wrapper = shallow(<ProductList products={mockProducts}/>);
expect(wrapper.find('li').length).toEqual(mockProducts.length); // 3
});

Immediately we’ll see that our new test is failing (remember this is actually what we want):

Our first failing test

The test explained

Disclaimer: I won’t explain any Jest or Enzyme APIs in any great detail here. They have great documentation for that.

Let’s take a look how the test above works. For those of you who like patterns and frameworks, our test just so happens to follow this pattern:

  • Arrange all the necessary preconditions.
  • Act on the object or method under test.
  • Assert that the expected results have occurred.

More specific to our test:

  • The precondition to rendering a ProductList component is that we have some list of products we can pass into it as props. We therefore arrange our test by first creating a mock of these products called mockProducts.
  • The object under test here is of course the ProductList component. To act on it we need to render it. To do this we use the shallow render method provided by Enzyme (see here if you’re curious).
  • Once rendered, we then assert that whether the expected results have occurred. In this case, did the component ‘render an unordered list of products` (our acceptance criteria)? To do this we first search the wrapper for all <li> elements and we then check that the number of these matches the number of products we passed in as props. Easy.

Making the test pass

The test is failing. That’s what we want. But now, step 2: make the test pass. To implement this functionality, all we need to do is map over the products passed in as props and render an <li> for each one. Our component in /src/components/ProductList.js should now look like:

import React from 'react';function ProductList (props) {
return (
<ul>
{
props.products.map(product => (
<li key={product.id}>
{product.name}
</li>
))
}

</ul>
)
}
ProductList.propTypes = {
products: React.PropTypes.array.isRequired
};
export default ProductList;

Our tests should now all be passing:

Victory

Let’s now reflect on whether we’ve correctly implemented all the acceptance criteria for our user story:

  • The list of products should be displayed as an unordered list.
  • Each product listing should include the product name and brand.

It seems that we’ve not yet tested a key part of the acceptance criteria: we should be showing the product name and brand name. We probably should’ve even written empty failing tests for these criteria at the start so that we didn’t forget. Anyway, let’s write those tests now and add them to /src/components/ProductList.test.jsx:

it('should display the product name in each `<li>` element', () => {
const mockProducts = [
{id: 1, name: 'Mock Product 1', brand: 'MockBrandA'},
{id: 2, name: 'Mock Product 2', brand: 'MockBrandB'},
{id: 3, name: 'Mock Product 3', brand: 'MockBrandC'},
];
const wrapper = shallow(<ProductList products={mockProducts}/>);
const firstElement = wrapper.find('li').first();
// Check that the product name is contained somewhere in this element
expect(firstElement.contains(mockProducts[0].name)).toEqual(true);
});
it('should display the brand name in each `<li>` element', () => {
const mockProducts = [
{id: 1, name: 'Mock Product 1', brand: 'MockBrandA'},
{id: 2, name: 'Mock Product 2', brand: 'MockBrandB'},
{id: 3, name: 'Mock Product 3', brand: 'MockBrandC'},
];
const wrapper = shallow(<ProductList products={mockProducts}/>);
const firstElement = wrapper.find('li').first();
// Check that the brand name is contained somewhere in this element
expect(firstElement.contains(mockProducts[0].brand)).toEqual(true);
});

The test output now shows us that only one test is failing: we already include the product name but we’re missing the brand name:

Let’s quickly make that test go green by adding the brand name in /src/components/ProductList.js:

// ... stuff<li key={product.id}>
{product.name} ({product.brand})
</li>
// ... more stuff

Our tests should now be all green.

Tests can be refactored too

While the “red, green, refactor” thing typically refers to refactoring the code itself, we can also refactor our tests when we need.

You might have noticed that our tests are getting a bit verbose and repetitive — we keep creating the same mockProducts and the same wrapper in each test. Let’s DRY this out using the beforeEach and afterEach hooks that Jest (and every other test runner) provide us. Your ProductList.test.js file should now look like:

import React from 'react';
import {shallow} from 'enzyme';
import ProductList from './ProductList';
let mockProducts, wrapper;beforeEach(() => {
// This is run before every test
mockProducts = [
{id: 1, name: 'Mock Product 1', brand: 'MockBrandA'},
{id: 2, name: 'Mock Product 2', brand: 'MockBrandB'},
{id: 3, name: 'Mock Product 3', brand: 'MockBrandC'},
];
wrapper = shallow(<ProductList products={mockProducts}/>);
});
it('should render an <li> element for every product in `props.products`', () => {
expect(wrapper.find('li').length).toEqual(mockProducts.length);
});
it('should display the product name in each `<li>` element', () => {
const firstElement = wrapper.find('li').first();
expect(firstElement.contains(mockProducts[0].name)).toEqual(true);
});
it('should display the brand name in each `<li>` element', () => {
const firstElement = wrapper.find('li').first();
expect(firstElement.contains(mockProducts[0].brand)).toEqual(true);
});

Testing user interaction

It’s great that we can now display a list of products — we are amazing. Let’s now look at our second user story:

As a user, I should be able to click a product and add it to my cart.

This perplexes us; we consult with our tech lead. He tells us we need to do three things in the short term:

  1. The ProductList component should be passed a function in its props called onProductSelect.
  2. It should call this function when an item in the list is clicked and pass it all the stuff about that product.
  3. It shouldn’t do anything else. State should not be managed in this component.

Let’s quickly update our PropTypes for our ProductList component:

ProductList.propTypes = {
products: React.PropTypes.array.isRequired,
onProductSelect: React.PropTypes.func.isRequired
};

Our tests will now throw warnings as we are rendering this component without our new prop (onProductSelect) being present in both the `App` component itself and in all of our tests. Let’s start fixing this by declaring a handler in /src/App.js component and passing that down:

import React, { Component } from 'react';
import ProductList from './components/ProductList';
class App extends Component {constructor(props) {
super(props);
this.state = {
selectedProducts: [],
products: [
{id: 1, name: 'AirMax 90', brand: 'Nike'},
{id: 2, name: 'Yeezy', brand: 'Adidas'},
{id: 3, name: 'Classic', brand: 'Reebok'},
]
}
}
handleProductSelect (product) {
this.setState(prevState => {
return {
selectedProducts: prevState.selectedProducts.concat(product)
}
});
}
render() {
return (
<div>
<h1>My Product Store</h1>
<p>You have selected {this.state.selectedProducts.length} product(s).</p>
<ProductList
products={this.state.products}
onProductSelect={this.handleProductSelect.bind(this)}
/>
</div>
);
}
}export default App;

It’s a pity this blog engine doesn’t allow us to highlight the diff like Github does, but these are the main things that changed:

  • In our constructor, we’ve added an empty array called selectedProducts to our state. We’ll use this to store what products have been selected.
  • We’ve added a method called handleProductSelect. It does all the “does not mutate state; instead returns a new array of selectedProducts” stuff (yes, the spread operator can be your friend too).
  • We pass this function to the ProductList component as a prop. For good measure, we do complicated stuff because of `this`.

Now onto the real stuff, the reason why we’re here, how do we test that ProductList is actually calling this function?

Spying on mock functions

We need to test that when a user click’s on a list element, the function we have passed in as a prop (onProductSelect) is called and it is called with the correct arguments. Let’s start by updating our test by defining a silly function that just logs to the console when we select a product:

let mockProducts, wrapper, productSelectFn;beforeEach(() => {

mockProducts = [
{id: 1, name: 'Mock Product 1', brand: 'MockBrandA'},
{id: 2, name: 'Mock Product 2', brand: 'MockBrandB'},
{id: 3, name: 'Mock Product 3', brand: 'MockBrandC'},
];
productSelectFn = (productSelected) => console.log('You selected', productSelected); wrapper = shallow(
<ProductList
products={mockProducts}
onProductSelect={productSelectFn}
/>
);
});

We can now write a test that clicks on an li element and see what happens:

it(‘should call `props.onProductSelect` when an <li> is clicked’, () => {
const firstElement = wrapper.find(‘li’).first();
firstElement.simulate(‘click’);
});

The simulate method is a nice little utility provided by the Enzyme ShallowWrapper API. If you scroll up in your test results, you will indeed see some reference to ‘Console’ in there.

However, we obviously don’t want our productSelectFn to call console.log. Instead, we want this function to represent some arbitrary “mock function”. We also want to be able to inspect this mock function to check if it has been called, how many times its been called, and what arguments was it called with. In short, we need to spy on it.

Luckily, Jest provides this type of mock function for us right out the box: jest.fn(). See here and here for the docs and API respectively.

jest.fn() is a constructor (or a factory? or something else?) that returns a function with some nice properties (see the API link for what those are). Some of you might be familiar with the more comprehensive SinonJS library. Think of jest.fn as Sinon’s smaller and slightly worse-looking brother.

Let’s update our beforeEach and afterEach statements to use Jest’s built-in mock function:

beforeEach(() => {
mockProducts = [
{id: 1, name: 'Mock Product 1', brand: 'MockBrandA'},
{id: 2, name: 'Mock Product 2', brand: 'MockBrandB'},
{id: 3, name: 'Mock Product 3', brand: 'MockBrandC'},
];
productSelectFn = jest.fn();
wrapper = shallow(
<ProductList
products={mockProducts}
onProductSelect={productSelectFn}
/>
);
});
afterEach(() => {
productSelectFn.mockReset();
});

The salient points above are that:

  • We change productSelectFn from console.log() to jest.fn()
  • We reset this mock function in the afterEach. Every time this function is called it will keep track of it. However, we want to make sure that in between tests we start on a clean slate.

Now that we have a mock function thats declared and passed in to our ProductList, we can now update our tests to spy on it as follows:

it('should call `props.onProductSelect` when an <li> is clicked', () => {
const firstElement = wrapper.find('li').first();
// We check that the function has not been called yet
expect(productSelectFn.mock.calls.length).toEqual(0);
// We click the <li>
firstElement.simulate('click');
// We check that the function has now been called
expect(productSelectFn.mock.calls.length).toEqual(1);
// We check it's been called with the right arguments
expect(productSelectFn.mock.calls[0][0]).toEqual(mockProducts[0]);
});

We’ve now spent quite some time setting up the spies but our tests still fail. Obviously — we didn’t update those <li> elements to call the function we passed in. Let’s update the ProductList component to do that now by changing the code to:

<li key={product.id} onClick={() => props.onProductSelect(product)}>
{product.name} ({product.brand})
</li>

What we’re doing above is calling the onProductSelect function we passed in, when the list element’s onClick event is triggered. The reason we ‘close over’ this (wrap in an anonymous function) is that onClick actually calls whatever function is passed in with the ‘event’ as the first argument. We don’t care about the event, we want to callonProductSelect with the product.

You can now check your test results and see that all is well. You can also check your actual browser and see that things are working (whaaaaaatt):

Let’s recap

We have now spent the last hour (or more) winning:

  • We wrote tests before writing any real code
  • It was easy to write tests
  • We made sure we addressed each business requirement (we wrote tests for each acceptance criteria)
  • Our code is better because we wrote tests
  • Not that it matters, but our ProductList component now has 100% test coverage (run npm test — — coverage or see coverage report below if you don’t believe me)

Next steps (for you, not me)

There are still a few stories that we didn’t cover. Why don’t you use your new found powers to develop them TDD-style:

  • As a user, I should be able to view the number of items in my cart (we actually have that displayed but we have no tests for it)
  • As a user, I should be able to filter the items listed

Your feedback on the above tutorial is greatly appreciated!

--

--

Adam Mausenbaum

Digital. Analytics. Software. Big Data. Cloud. Buzzwords.