Sanjay Purswani

@sanjsanj

A guide to TDD a React/Redux TodoList App — Part 2

Part 1 — Link.

Part 2 — You’re here now.

Part 3 — Link.

Part 4 — Link.

Add a todo

Now let’s write a more useful feature test that will help us write corresponding unit tests and code to actually build the functionality we want:

e2etests/test.js

Let’s break it down in plain english.

Line 9 Create a todoText string constant.

Line 10 Navigate to the page.

Line 11 Find the .todo-input element and enter the todoText.

Line 12 Click the .todo-submit element.

Line 13 Find the .todo-text element and assign its text value to the actual const.

Line 15 Assert that actual is equal to todoText.

Run the test and watch it fail. You should have 1 passing and 1 failing e2e test, if both are failing you have an issue with watch-mode and should run the e2e tests manually. I haven’t had time to properly debug this watch issue yet but go right ahead if you’re suitably motivated.

Before we write the necessary code we’re going to write appropriate unit tests, and before that let’s fix the eslint errors we have in our e2e test. Add the below comment to the top of the e2etests/test.js file:

/* global describe, it, browser */

If we get another eslint error in that file complaining about chai being a devDependency there is a known bug that has caused some debate over eslint rule merging. It’s not relevant to what we’re doing here but if you want to know more start reading here.

The fix for the moment is to add another rule to our .eslintrc.js

rules: {
...
"import/no-extraneous-dependencies": [2, { devDependencies: true }],
},

This is what my .eslintrc.js looks like now:

.eslintrc.js

Let’s create the following folder structure src/components/addTodo/ and within that make a placeholder index.js and leave it empty. We don’t want to write any code we don’t need, and we only need just enough code to make our tests pass. So let’s write a unit test in src/components/addTodo/test.js :

src/components/addTodo/test.js

Line 5 Import an AddTodo component from the index.js in the same folder as this test.

Line 9 — 10 Shallow render the component to the component const and expect that it exists.

This unit test should fail because our .../addTodo/index.js returns nothing and therefore can not render. So let’s create our component:

You should just leave your unit tests task running, if it isn’t then run npm run test.

src/components/addTodo/index.js

Now our test should pass. Let’s write the next test, the minimum we have to do to inch closer to our end goal:

src/components/addTodo/test.js

The test should fail, let’s do the minimum to make it pass:

src/components/addTodo/index.js

At this point we can incorporate our AddTodo component in to our main App, this should help us pass the first error in our e2e test and show us what more we need to do:

src/App.js

Line 2 import the AddTodo component.

Line 7 Add it to our App.

Line 6 Add an appropriate heading.

Our e2e test should now show us that it was able to find the .todo-input and enter some text in it, but it was not able to find the .todo-submit , so that’s exactly what we’re going to unit test and code next:

src/components/addTodo/test.js

So let’s add the .todo-submit element that we need, knowing we’ll want it to be a button. Now I know what you’re thinking, ‘An input, a submit button, what next — a form?’, yes you’re right, a form!

src/components/addTodo/index.js

Our e2e test can enter text in to the input and click on the submit button, however we need to hook up the functionality for it, but let’s write an appropriate unit test first:

src/components/addTodo/test.js

Line 1 Add an exception to eslint for jest.

Line 14 We assign the jest.fn() method as our mock. We use this because it comes bundled with our app and gives us nice helper methods to make it easy to test that the function was called whilst not worrying about it’s actual implementation.

Line 4 We import the mount module from enzyme.

Line 15 We actually mount our component so we have access to the event to preventDefault() on the form submission. We’re also instantiating it with a submitTodo prop which we’re giving our submitMock function.

<AddTodo submitTodo={submitMock} /> translates to; Create an AddTodo component, it has a property called submitTodo, when we instantiate the component pass in submitMock as that property. Later in the component itself we’ll write some validation for the prop so we ensure it’s a function.

Line 17 — 19 We expect that our submitTodo starts off with zero calls, and once we submit our form it has one call, confirming the event was triggered.

We’re about to make substantial changes to our AddTodo component, but first let’s install the prop-types node package:

npm install --save-dev prop-types

src/components/addTodo/index.js

Let’s walk through the changes:

Line 4 — 7 We’ve changed the syntax of our component a little. Because we want to do more than just return some markup we’ve gone from the starting construction of:

const Something = () => (
<p>Return something</p>
);

To:

const Something = () => {
const text = "Return something";
  return (
<p>{text}</p>
);
};

Then:

Line 5 We’re going to need a mutable variable to store our input text, so we do a let input;.

Line 19 — 21 When we submit our form we want to send the input’s text so we tell React to store a ref (reference) to this DOM element, that means that React will be able to track and use it in the component. And then assign the element to the mutable input var.

There are better ways to do the above but I think it’s a nice opportunity to explore the functionality of a ref , read more here.

Line 10 — 14 We tell the form what to do onSubmit. Firstly preventDefault actions, in this case reloading the page since that’s the default behaviour of form submission. Then call a submitTodo function with the input.value, in this case the text that is entered in to the input. Then set the input.value back to an empty string so it clears the input text box.

Line 4 We pass the submitTodo function in to our component as a property, aka prop. Read about components and props.

Line 2 We have to import the prop-types node package to give us prop validation and defaults when we don’t provide values for them.

Line 32 — 34 We have to validate the prop in our component so we tell React it’s going to be a function and it’s required when we instantiate this component. Read about validating property types.

The test we just wrote passes but our changes to the AddTodo component have broken two other tests. We’re now telling the component that it requires a submitTodo prop when instantiated, it’s a function, it’s required, and we haven’t provided a default.

// src/components/addTodo/index.js
// Line 32 - 34
AddTodo.propTypes = {
submitTodo: PropTypes.func.isRequired,
};

Now in our tests we want each of the instances of the component to also have this prop, but this is going to be the first of a number of refactors so let’s optimise our setup a little, remembering DRY (Don’t repeat yourself).

src/components/addTodo/test.js

Line 8 — 17 We create a mutable component variable and assign it our shallow mounted component beforeEach test, and remove the assignment from each individual it(...) block.

Line 33 We still have to keep this assignment because we actually want to mount it when rendering for this test.

Line 1 Add beforeEach to the eslint exceptions.

Now we have our App test to fix. It’s telling us exactly where the error has occurred, App.js Line 7 :

src/App.js

Line 7 For now we’re just passing an empty ES6 arrow function () => {} in to our AddTodo ‘s submitTodo prop. It will allow us to pass our component validation and have passing unit tests once again, although it won’t help our e2e test progress or actually add any functionality yet… but that’s what we’ll do next.

What our App looks like now.

Recap

We’ve learned how to:

  • Write a useful feature test.
  • Unit test the creation of a React component.
  • Validate property types.

Up next

Now we need to go beyond just creating UI and start setting up our event dispatchers via actions and event handlers via reducers.

Part 1 — Link.

Part 2 — You’re here now.

Part 3 — Link.

Part 4 — Link.

More by Sanjay Purswani

Topics of interest

More Related Stories