Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
All Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Newsletter Hub
Free Learning
Arrow right icon
timer SALE ENDS IN
0 Days
:
00 Hours
:
00 Minutes
:
00 Seconds
Mastering React Test-Driven Development
Mastering React Test-Driven Development

Mastering React Test-Driven Development: Build rock-solid, well-tested web apps with React, Redux and GraphQL

eBook
€34.99
Paperback
€43.99
Subscription
Free Trial
Renews at €11.99p/m

What do you get with Print?

Product feature icon Instant access to your digital copy whilst your Print order is Shipped
Product feature icon Paperback book shipped to your preferred address
Product feature icon Redeem a companion digital copy on all Print orders
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
Product feature icon AI Assistant (beta) to help accelerate your learning
OR
Modal Close icon
Payment Processing...
tick Completed

Shipping Address

Billing Address

Shipping Methods
Table of content icon View table of contents Preview book icon Preview Book

Mastering React Test-Driven Development

First Steps with Test-Driven Development

This book follows a simple format: it's a walk-through of building React applications using a test-driven approach. We'll touch on many different parts of the React experience, including building forms, composing interfaces, and animating elements. We'll also integrate React Router, Redux, and GraphQL, all guided by tests. The focus isn't on how these features of React work, but rather on how to test them and make sure you're using them with confidence.

Modern JavaScript programmers rely heavily on packages that other people have developed. This allows us to concentrate on innovating, not reinventing, the wheel. The downside, however, is that we don't always have a full understanding of the technologies we’re dealing with. We simply don't need to learn them.

Among other things, Test-Driven Development (TDD) is an effective technique for learning new frameworks and libraries. That makes it very well suited for a book on React and its ecosystem. This book will allow you to explore React in a way that you may not have experienced before.

If you're new to TDD, some of the steps outlined may leave you scratching your head. You may find yourself wondering why we're going to such Herculean efforts to build an application. There is tremendous value to be gained in specifying our software in this way. By being crystal clear about our requirements, we gain the ability to adapt our code without fear of change. We gain automated regression testing by default. Our tests comment our code, and those comments are verifiable when we run them. We gain a method of communicating our decision-making process with our colleagues. And you'll soon start to recognize the higher level of trust and confidence you have in the code you're working on. If you're anything like me, you'll get hooked on that feeling and find it hard to work without it.

Sections 1 and 2 of this book involve building an appointments system for a hair salon—nothing too revolutionary, but as sample applications go, it offers plenty of scope. We'll get started with that in this chapter. Sections 3 and 4 use an entirely different application: a Logo interpreter. Building that offers a fun way to explore more of the React landscape.

This chapter, and in fact this whole book, takes a first principles approach to React. We start with minuscule steps to slowly uncover the TDD story. We'll prefer rolling our own code to using libraries and packages. We will start from an empty directory and begin building out our application, test by test. Along the way, we’ll discover a lot of the fundamental ideas behind test-driven development and React.

The following topics will be covered in this chapter:

  • Creating a new React project from scratch
  • Displaying data with your first test
  • Refactoring your work
  • Writing great tests
  • Rendering lists and detail views

Technical requirements

Later in this chapter, you'll be required to install Node Package Manager (npm) together with a whole host of packages. You'll want to ensure you have a machine capable of running the Node.js environment.

You'll also need access to the command line.

In addition, you should choose a good editor or Integrated Development Environment (IDE) to work with your code.

Creating a new React project from scratch

There's a standard template for creating React apps: the create-react-app application template. This includes some standard dependencies and boilerplate code that all React applications need. However, it also contains some extra items such as favicon.ico, a sample logo, and CSS files. While these are undoubtedly useful, having them here at the very start of a project is at odds with one of the test-driven developer's core principles: You Ain't Gonna Need It (YAGNI).

This principle says that you should hold off adding anything to your project until you're really sure that it's necessary. Perhaps that's when your team adds a user story for it into the iteration, or maybe it's when a customer asks for it. Until then, YAGNI.

It's a theme that runs throughout this book and we'll start right now by choosing to avoid create-react-app. You can always start every JavaScript project from scratch, and there's a certain joy to be found in going over the basics each time.

Installing NPM

We’ll be making extensive use of the npm command-line tool and the Node.js execution environment. Each time you run your tests, which will be very frequently, you'll be required to run an npm command.

Toward the end of the chapter, we'll also use npm to package our application.

You can find out if you already have it installed on your machine by opening a Terminal window (or Command Prompt if you’re on Windows) and typing the following:

npm -v

If the command isn’t found, head on over to the Node.js website for details on how to install. The URL is included at the end of this chapter.

The npm program knows how to update itself, so if it's installed, I recommend you ensure you’re on the latest version. You can do this on the command line by typing this:

npm install npm@latest -g

I'm using version 6.9.0 to write this book. If you have any issues with the code samples contained here, differing NPM versions could be one of the causes, so please bear that in mind as you continue.


Yet another resource negotiator (YARN) is an alternative to NPM, and I won’t hold it against you if you choose to use it. There are only a handful of npm commands in this book—I assume that if you’re sticking with YARN, then you’ll already know how to convert npm commands to yarn commands.

Creating a new Jest project

The Git tag for this section is starting-point. It doesn't contain any code; just a README.md file. If you want to follow along using the book's Git repository then you should ensure you've branched from this tag. Detailed instructions from doing that are in the Getting started before Chapter 1 section of the Preface.

Now that NPM is installed, we can create our project:

  1. If you're following along with the book's Git repository, open a Terminal window and navigate to the repository directory that you cloned in the Getting started before Chapter 1 section of the Preface. Otherwise, simply navigate to your local projects directory.
  2. Create a new directory using mkdir appointments and then change to it using cd appointments.
  3. Enter the npm init command, which begins the process of initializing a new NPM project and generating a package.json file for you.
  4. The first questions ask you to provide a package name, version, description, and an entrypoint. Since we're building an appointments system, you can call it appointments. Accept the default version (by just hitting Enter), and enter a description of Appointments system. You can accept the default entrypoint too.
  5. Next, you'll be asked for a test command, for which you should type in jest. This will enable you to run tests by using the npm test shortcut command.
Don't worry if you miss this; you can set it afterward by adding "test": "jest" to the scripts section of the generated package.json.
  1. You'll be asked to specify a repository, which you could just set as example.com for now. If you don’t fill these fields in, npm will print warnings every time you run a command.
  1. You can accept the defaults for everything else.
You may wonder why we filled out the repository field. TDD loves fast feedback cycles. Prioritize cleaning your screen and command outputs of as much noise as possible. Any time you see something that is destroying clarity, either fix it right then and there, or put it as an action at the top of your to-do list.

In this particular case, you could also add "private": true to your package.json, instead of setting the repository field.
  1. Hit Enter on the remaining questions to finish the initialization process.
  2. Install Jest using npm install --save-dev jest.

You will see the bottom line of your Terminal fill up with a fast-changing stream of package information as NPM installs dependent packages (a paltry 553 packages at the time of writing). You may see some warnings depending on the platform you are installing on, but these can be ignored. Once complete, you should see this:

npm notice created a lockfile as package-lock.json. You should commit this file.

+ [email protected]
+ added 553 packages from 373 contributors and audited 849842 packages in 16.304s
+ found 0 vulnerabilities

Bringing in React and Babel

Let's install React. That's actually two packages:

npm install --save react react-dom

React makes heavy use of JavaScript XML (JSX), which we need Babel to transpile for us. Babel also transpiles our modern ES6 and ES7 constructs for us.

The following information is accurate for Babel 7. If you're using a later version, you may need to adjust the installation instructions accordingly.

Thankfully, Jest already includes Babel, so we just need to install presets and plugins:

npm install --save-dev @babel/preset-env @babel/preset-react
npm install --save-dev @babel/plugin-transform-runtime

npm install --save @babel/runtime

A Babel preset is a set of plugins. Each plugin enables a specific feature of the ECMAScript standards, or a preprocessor such as JSX.

The env preset brings in essentially everything possible. It should really be configured with target execution environments. See the Further reading section at the end of this chapter for more information.

We need to enable the packages we've just installed. Create a new file, .babelrc, and add the following:

{
"presets": ["@babel/env", "@babel/react"],
"plugins": ["@babel/transform-runtime"]
}

With that, you're all set to write some tests. You may wish to check in at this point.

Displaying data with your first test

The Git tag for this section is appointment-first-name.

In this section, we'll discover the TDD cycle for the first time.

We'll start our application by building out an appointment view. We won't get very far; the tests we'll create in this chapter will simply display the customer who made the appointment. As we do so, we'll discuss the TDD process in detail.

We'll build a React functional component called Appointment. It is used for displaying the details of a single appointment in our system. The component will be passed in a data structure that represents Appointment, which we can imagine looks a little something like this:

{
customer: { firstName: 'Ashley', lastName: 'Jones', phoneNumber: '(123) 555-0123' },
stylist: 'Jay Speares',
startsAt: '2019-02-02 09:30',
service: 'Cut',
notes: ''
}

We won't manage to get all of that information displayed by the time we complete the chapter; in fact, we'll only display the customer's firstName, and we'll make use of the startsAt timestamp to order a list of today's appointments.

But before we get on to that, let's explore Jest a little.

Writing a failing test

What exactly is a test? We'll discover that by writing one. In your project directory, type the following commands:

mkdir test
touch test/Appointment.test.js

Open the test/Appointment.test.js file in your favorite editor or IDE and enter the following:

describe('Appointment', () => {
});

The describe function defines a test suite, which is simply a set of tests with a given name. The first argument is the name (or description) of the unit you are testing. It could be a React component, a function, or a module. The second argument is a function inside of which you define your tests.

All of the Jest functions are already required and available in the global namespace when you run the npm test command. You don't need to import anything.

For React components, it's good practice to give your describe blocks the same name as the component itself.

You should run this code right now in the Jest test runner. It will give us valuable information about what to do next. You might think that running tests now is pointless, since we haven't even written a test yet, but with TDD, it's normal to run your test runner at every opportunity.

On the command line, run the npm test command:

> [email protected] test /home/daniel/work/react-tdd/ch1
> jest

FAIL test/Appointment.test.js
● Test suite failed to run

Your test suite must contain at least one test.

at node_modules/jest/node_modules/jest-cli/build/TestScheduler.js:225:24

Test Suites: 1 failed, 1 total
Tests: 0 total
Snapshots: 0 total
Time: 0.917s
Ran all test suites.
npm ERR! Test failed. See above for more details.

You can see Jest helpfully tells us Your test suite must contain at least one test. Test-driven developers rely heavily on listening to the test runner and what it tells us. It usually tells them exactly what to do next. In this case, it's telling us to create a test. So, let's do that.

Where should you place your tests?

If you do try out the create-react-app template, you’ll notice that it contains a single unit test file, App.test.js, which exists in the same directory as the source file, App.js.

I don't recommend mixing production code with test code. For a start, it isn’t the conventional unit-testing approach, which uses two separate directories for production code and test code. More importantly, however, it’s likely that you won’t have a one-to-one mapping between production and test files.

Writing your first expectation

Change your describe call to this:

describe('Appointment', () => {
it('renders the customer first name', () => {
});
});

The it function defines a single test. The first argument is the description of the test and always starts with a present-tense verb, so that it reads in plain English. The it in the function name refers to the noun you used to name your test suite (in this case, Appointment). In fact, if you run tests now, with npm test, remember, it should make sense:

PASS test/Appointment.test.js
Appointment
✓ renders the customer first name (1ms)

You can read the describe and it descriptions together as one sentence: Appointment renders the customer first name. You should aim for all of your tests to be readable in this way.

As we add more tests, Jest will show us a little checklist of passing tests.

You may have used the test function for Jest, which is equivalent to it. Since we’re doing behavior driven development style of TDD, you should stick with it.

Empty tests, such as the one we just wrote, always pass. Let's change that now. Let's add an expectation to our test. Change test to read as follows:

it('renders the customer first name', () => {
expect(document.body.textContent).toMatch('Ashley');
});

This expect call is an example of a fluent API. Like the test description, it reads like plain English. You can read it like this: I expect document.body.textContent toMatch the string Ashley.

Although it might look complicated, it's quite a simple idea: each expectation has an expected value that is compared against a received value. In this example, the expected value is Ashley and the received value is whatever is stored in document.body.textContent.

The toMatch function is called a matcher and there are a whole lot of different matchers that work in different ways. In this case, the expectation passes if document.body.textContent has the word Ashley anywhere within it.

Each individual test can have as many expectations in it as you like, and we'll see examples of multiple expectations in a test later in this chapter.

Before we run this test, spend a minute thinking about the code. You might have guessed that the test will fail. The question is, how will it fail?

Let's run test now, with npm test, and find out:

FAIL test/Appointment.test.js
Appointment
✕ renders the customer first name (10ms)

● Appointment › renders the customer first name

expect(received).toMatch(expected)

Expected value to match:
"Ashley"
Received:
""

1 | describe('Appointment', () => {
2 | it('renders the customer first name', () => {
> 3 | expect(document.body.textContent).toMatch('Ashley');
| ^
4 | });
5 | });
6 |

at Object.toMatch (test/Appointment.test.js:3:39)

There are four parts to the test output that are relevant to us:

  • The name of the failing test
  • The expected answer
  • The actual answer
  • The location in the source where the error occurred

All of these help us to pinpoint where our tests failed: document.body.textContent is empty. This isn't surprising really, since we've not done anything to set the body text.

But, hold on a second. Where did document.body come from? No one defined that yet. Shouldn’t we expect the test to fail with an error saying that the document is undefined?

Jest magically includes a DOM implementation for us, which is why we have access to document and document.body. It uses jsdom, a headless implementation of the DOM. We can do test browser interactions on the command line, which is much simpler than involving a browser in our work.

In Jest lingo, this is called the Jest environment and it defaults to jsdom. If you want to verify that this is happening, add the following config to your package.json file:

"jest": {
"testEnvironment": "node"
}

Re-run tests and observe the different output to convince yourself that JSDOM is no longer present.

Be sure to remove this extra configuration before you continue, as we’ll be relying on the JSDOM environment from now on.

Rendering React from a test

In order to make this test pass, we'll have to write some code above the expectation that will call into our production code.

Since we're testing what happens when a React component is rendered, we'll need to call the ReactDOM.render function. This function takes a component (which in our case will be called Appointment), performs the React render magic, and replaces an existing DOM node with the newly rendered node tree. The DOM node it replaces is known as the React container.

Here's the method signature:

ReactDOM.render(component, container)

In order to call this in our test, we'll need to define both component and container. Let's piece the test together before we write it out in full. It will have this shape:

it('renders the customer first name', () => {
const component = ???
const container = ???
ReactDOM.render(component, container);
expect(document.body.textContent).toMatch('Ashley');
});

Since we're rendering Appointment, we know what we need to put for component. It's a JSX fragment that takes our customer as a prop:

 const customer = { firstName: 'Ashley' };
const component = <Appointment customer={customer} />;
Back when we were considering our design, we came up with a whole object format for our appointments. You might think the definition of a customer here is very sparse, as it only contains a first name. But we don't need anything else for a test about customer names.

What about container? We can use the DOM to create a container element:

const container = document.createElement('div');
document.body.appendChild(container);

Now let's take a look at that test in full. Change your test in test/Appointments.test.js to match the following:

it('renders the customer first name', () => {
const customer = { firstName: 'Ashley' };
const component = <Appointment customer={customer} />;
const container = document.createElement('div');
document.body.appendChild(container);

ReactDOM.render(component, container);

expect(document.body.textContent).toMatch('Ashley');
});

As we're using both ReactDOM and JSX, we'll need to include the two standard React import at the top of our test file for this to work, as follows:

import React from 'react';
import ReactDOM from 'react-dom';

Go ahead and run the test. Within the output, you'll see the following:

ReferenceError: Appointment is not defined

This is subtly different from the test failure we saw previously. This is a run-time exception, not an expectation failure. Thankfully, though, the exception is telling us exactly what we need to do, just as a test expectation would. We need to define Appointment.

Make it pass

We're now ready to make failing test pass:

  1. Add import to test/Appointment.test.js, below the two React imports:
import { Appointment } from '../src/Appointment';
  1. Run tests with npm test. You'll get a different error this time:
Cannot find module '../src/Appointment' from 'Appointment.test.js'
Although Appointment was defined as an export, it wasn't defined as a default export. That means we have to import it using the curly brace from of import (import { ... }). I tend to avoid using default exports; doing so keeps the name of my component and its usage in sync: if I change the name of a component, then every place where it's imported will break unless I change those too. This isn't the case with default exports. Once your names are out of sync, it can be hard to track where components are used.
  1. Let's create that module. Type the following at your command line:
mkdir src
touch src/Appointment.js
  1. In your editor, add the following content to src/Appointment.js:
export const Appointment = () => {};

Why have I created a shell of an Appointment without actually creating an implementation? This might seem pointless, but another core principle of the test-driven developer is always do the simplest thing to pass the test. We could rephrase this as always do the simplest thing to fix the error you're working on.

Remember when I mentioned that we listen carefully to what the test runner tells us? In this case, the test runner said Cannot find module Appointment, so what was needed was to create that module:

  1. Run npm test. You'll get a lot of React output as a large stack trace. If you scroll up to the top, you'll see this:
Error: Uncaught [Invariant Violation: Appointment(...): Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null.]
  1. To fix that, we need to do what it's telling us: we need to return something "from render". So, let's return something. Change the file to read as follows:
import React from 'react';

export const Appointment = () => <div></div>;
  1. Now, if you run the test, you should get a test failure:
FAIL test/Appointment.test.js
Appointment
✕ renders the customer first name (23ms)

● Appointment › renders the customer first name

expect(received).toMatch(expected)

Expected value to match:
"Ashley"
Received:
""
  1. To fix the test, change the Appointment definition to look like this:
export const Appointment = () => (
<div>Ashley</div>
);

But, wait a second. This test isn't using our appointment variable that we defined in our test. We just hard-coded a value of Ashley in there!

Remember our principle: always implement the simplest thing that will possibly work. That includes hard-coding, when it's possible. In order to get to the real implementation, we need to add more tests. This process is called triangulation. The more specific our tests get, the more general our production code needs to get.

This is one reason why pair programming using TDD can be so fun. Pairs can play ping pong. Sometimes, your pair will write a test that you can solve trivially, perhaps by hard-coding, and then you force them to do the hard work of both tests by triangulating. They need to remove the hard-coding and add the generalization.

Let's triangulate:

  1. Make a copy of your first test, pasting it just under the first test, and change the test description and the name of Ashley to Jordan, as follows:
it('renders another customer first name', () => {
const customer = { firstName: 'Jordan' };
const component = <Appointment customer={customer} />;
const container = document.createElement('div');
document.body.appendChild(container);

ReactDOM.render(component, container);

expect(document.body.textContent).toMatch('Jordan');
});
  1. Run tests with npm test. We expect this test to fail, and it does. Take a careful look at this output:
FAIL test/Appointment.test.js
Appointment
✓ renders the customer name (19ms)
✕ renders another customer name (20ms)

● Appointment › renders another customer name

expect(received).toMatch(expected)

Expected value to match:
"Jordan"
Received:
"AshleyAshley"

Yes, it did fail—but with the text AshleyAshley!

This kind of repeated text is an indicator that our tests are not running independently of one another. There is some shared state that isn't being cleared. We need to change course and uncover what's going on.

Unit tests should be independent of one another. The simplest way to achieve this is to not have any shared state between tests. Each test should only use variables that it has created itself.

Backtracking on ourselves

There's only one piece of shared state that our tests use and that's document. It must not be getting cleared each time the tests are run, and so we see the output of each test inside the document.

Even if we fixed our production code to remove the hard-coding, it still wouldn't pass; instead, we'd see the text AshleyJordan.

One solution is to clear the document DOM tree before each test run. But there's a simpler solution: we can rework our tests to not append our container element to the DOM at all, and instead work directly with the container element. In other words, we can change our expectation to check not document.body.textContent but container.textContent.

There may come a time that we actually need to attach our nodes to the DOM, and at that point, we'll need to fix this problem properly. But for now, you ain't gonna need it. So, let's solve this by avoiding the DOM tree altogether. It's the simplest way forward.

Unfortunately, there's a problem. We're in the middle of a red test. We should never refactor, rework, or otherwise change course while we're red.

What we'll have to do is ignore, or pend, this test we're working on. We do that by changing the word it to it.skip. Do that now for the second test:

it.skip('renders another customer first name', () => {

Run tests. You'll see Jest ignores the second test, and the first one still passes:

PASS test/Appointment.test.js
Appointment
✓ renders the customer first name (19ms)
○ skipped 1 test

Test Suites: 1 passed, 1 total
Tests: 1 skipped, 1 passed, 2 total

For this refactor, we need to make two changes:

  • Change the expectation to match on container.textContent.
  • Remove the line that calls appendChild on the document body.

We can also take this opportunity to inline the component variable. Change the test to read as follows:

it('renders the customer first name', () => {
const customer = { firstName: 'Ashley' };
const container = document.createElement('div');
ReactDOM.render(<Appointment customer={customer} />, container);
expect(container.textContent).toMatch('Ashley');
});

Run your tests: the result should be the same as earlier, with one passing test and one skipped.

It's time to bring that second test back in, by removing the .skip from the function name, and this time, let's update the test code to make the same changes we made in the first, as follows:

it('renders another customer first name', () => {
const customer = { firstName: 'Jordan' };
const container = document.createElement('div');
ReactDOM.render(<Appointment customer={customer} />, container);
expect(container.textContent).toMatch('Jordan');
});

Running tests now should give us the error that we were originally expecting:

FAIL test/Appointment.test.js
Appointment
✓ renders the customer first name (18ms)
✕ renders another customer first name (8ms)

● Appointment › renders another customer first name

expect(received).toMatch(expected)

Expected value to match:
"Jordan"
Received:
"Ashley"

To fix this, we need to introduce the variable and use it within our JSX, which supports embedding JavaScript expressions within elements. We can also use destructuring assignment to avoid creating unnecessary variables.

Change the definition of Appointment to look as follows:

export const Appointment = ({ customer }) => (
<div>{customer.firstName}</div>
);

Note that I haven't fully destructured this. I could have written this function like this:

export const Appointment = ({ customer: { firstName } }) => (
<div>{firstName}</div>
);

The first version is no longer than the second; however, if you're counting tokens, it has one less set of curly braces. The most concise solution always wins!

Run tests; we expect this test to now pass, as follows:

PASS test/Appointment.test.js
Appointment
✓ renders the customer first name (21ms)
✓ renders another customer first name (2ms)

Great work! We're done with our passing test.

Refactoring your work

The next step of the TDD cycle is to refactor your work. This step is often the hardest, because our natural impulse can be to get straight into the next feature. Chasing green, as I like to call it: building more and more functionality is much more exciting. Refactoring, however, is much more zen.

The rule "more haste; less speed" applies to coding, just as in many other areas of life. If you skip the refactoring phase, your code quality will deteriorate. If you develop a habit of skipping refactoring, your code base will soon become difficult to work with.

It takes a lot of personal discipline to consistently refactor, but you will reap the rewards of a code base that remains maintainable as it ages.

Right now, we have some repeated code between our two tests. Let's fix that.

Test code needs as much care and attention as production code. The number one principle you'll be relying on when refactoring your tests is Don't Repeat Yourself (DRY). Drying up tests is a phrase all TDDers repeat often.

Promoting variables

Both of our tests use the same two variables: container and customer. We can pull up these declarations to the outer describe scope, but leave the definitions within the tests. Since we'll be splitting declaration and definition, that also means we'll need to use let instead of const:

Just above the first test, write the following two lines:

let container;
let customer;

Then, remove the word const from both of the tests, and re-run your tests, which should still be passing.

Using a beforeEach block

Both of our tests start with some setup or arrangement. When that setup is common to all tests, we can promote them into a beforeEach block instead. Code in this block is executed before each test.

Above your first test, write the following code, and delete the corresponding call to createElement from each of your two tests:

beforeEach(() => {
container = document.createElement('div');
});

Since we defined container in the scope of the describe block, the value set here in the beforeEach block will be available to our test once it executes.

Be careful when you use variables defined within the describe scope. These variables are not cleared between each test execution, so you are running the risk of non-independent tests. Therefore, any variable you declare in the describe scope should be assigned to a new value in a corresponding beforeEach block, or in the first part of each test, just as we've done here.

Extracting methods

The call to ReactDOM.render is the same in both methods. Since it's the same in both methods, it makes sense to pull it out.

However, rather than pull it out as-is, we can create a new function that takes the Appointment component as its parameter. This way, we can clearly see how our test data objects are woven through the object under test. If we hid that within an extracted method, the test would be less clear.

The parts of a test that you want to see are the parts that differ between tests. Usually, some data remains the same (container in this example) and some differs (customer in this example). Do your best to hide away whatever is the same and proudly display what differs.

Above the first test, write the following definition:

const render = component => ReactDOM.render(component, container);

Now, replace the call to ReactDOM.render in each test with this line:

render(<Appointment customer={customer} />);

Re-run your tests now—they should still be passing.

Writing great tests

The first test now looks like this:

it('renders the customer first name', () => {
customer = { firstName: 'Ashley' };
render(<Appointment customer={customer} />);
expect(container.textContent).toMatch('Ashley');
});

This is concise and clearly readable.

A good test has three distinct sections:

  • Arrange: Sets up test dependencies
  • Act: Executes production code under test
  • Assert: Checks expectations are met

A great test is not just good but is also the following:

  • Short
  • Descriptive
  • Independent of other tests
  • Has no side-effects

Red, green, refactor

We’ve covered a lot of ground, and we have gone into excruciating detail for a very simple test. All of the ground work is now done for us to speed up.

Let's look at the red, green, refactor cycle:

The steps of the TDD cycle are as follows:

  1. Write a failing test: Write a short test that describes some functionality you want. Execute your test and watch it fail. If it doesn’t fail, then it's not a good test; go back and try again.
  2. Make it pass: Make the test green. Do the simplest thing that will work. Feel free to make a mess; you can clean it up later.
  3. Refactor your code: Stop, slow down, and resist the urge to move on to the next feature. Work hard to make your code—both production and test code—as clean as it can be.

Streamlining your testing process

Think about the effort you've put into this book so far. What actions have you been doing the most? Most likely, you've been doing these:

  • Switching between src/Appointment.js and test/Appointment.test.js
  • Running npm test

To solve the first issue, you should use split-screen functionality in your editor. If you aren't already using that, then take this opportunity to learn how to do it. Load your production module on one side and the corresponding unit test file on the other. Here's a picture of my setup:

You can see that I also have a little test window at the bottom for showing test output.

Jest can also watch your files and auto-run tests when they change. To enable this, change the test command in package.json to jest --watchAll. This reruns all of your tests when it detects any changes.

Jest has an option to run only the tests in files that have changed, but you’ll find that since your React app will be composed of many different files, each of which is interconnected, it's better to run everything, as breakages can happen in many modules.

Rendering lists and detail views

The Git tag for this section is appointments-day-view.

So far, we’ve seen a great deal of test-driven development, but not much of React. In this section, we’ll take what we’ve learned about TDD and apply it to learning more React.

Our app at the moment just displays a single thing—a customer’s name. Now, we'll extend it so that we have a view of all appointments that are happening today.

Let's do a little more up-front design. We've got an Appointment component that takes an appointment and displays it. We can build an AppointmentsDayView component around it that takes an array of appointment objects and displays them as a list. It also displays a single Appointment component at any one time, whichever appointment is currently selected. The user can click on an Appointment and it will open up that appointment for viewing:

Rendering the list of appointments

We'll add our new component into the same file we've been using already because there's not much code in there so far.

We don't always need a new file for each component, particularly when the components are short functional components, such as our Appointment component (a one-line function). It can help to group related components or small sub-trees of components in one place.

In test/Appointment.test.js, create a new describe block under the first one, with a single test, as follows. This test checks that we render a div with a particular ID. That's important in this case because we load a CSS file that looks for this element. The expectations in this test use the DOM method, querySelector. This searches the DOM tree for a single element with the tag provided:

describe('AppointmentsDayView', () => {
let container;

beforeEach(() => {
container = document.createElement('div');
});

const render = component =>
ReactDOM.render(component, container);

it('renders a div with the right id', () => {
render(<AppointmentsDayView appointments={[]} />);
expect(container.querySelector('div#appointmentsDayView')).not.toBeNull();
});
});
It isn't always necessary to wrap your component in a div with an ID or a class. I tend to do it when I have CSS that I want to attach to the entire group of HTML elements that will be rendered by the component, which, as you'll see later, is the case for AppointmentsDayView.

This test uses the exact same render function from the first describe block, as well as the same let container declaration and beforeEach block. In other words, we've introduced duplicated code. By duplicating code from our first test suite, we're making a mess straight after cleaning up our code! Well, we're allowed to do it when we're in the first stage of the TDD cycle. Once we've got the test passing, we can think about the right structure for the code.

Run npm test and let's look at the output:

FAIL test/Appointment.test.js
Appointment
✓ renders the customer first name (18ms)
✓ renders another customer first name (2ms)
AppointmentsDayView
✕ renders a div with the right id (7ms)

● AppointmentsDayView › renders a div with the right id

ReferenceError: AppointmentsDayView is not defined

Let's work on getting this test to pass!

  1. To fix this, change the last import in your test file to read as follows:
import {
Appointment,
AppointmentsDayView
} from '../src/Appointment';
  1. In src/Appointment.js, add this functional component below Appointment:
export const AppointmentsDayView = () => null;
When we first defined our Appointment component earlier, we didn't return null. In fact, we didn't return anything. React then gave us a test error that we needed to fix before we got to a helpful test failure. So, returning null allows us to skip past the error from React and will bring us directly to a test failure. I'll generally begin all my components in this way—with a null value.
  1. Run your tests again:
  ● AppointmentsDayView › renders a div with the right id

expect(received).not.toBeNull()

Received: null

47 | it('renders a div with the right id', () => {
48 | render(<AppointmentsDayView appointments={[]} />);
> 49 | expect(container.querySelector('div#appointmentsDayView')).not.toBeNull();
| ^
50 | });
  1. Finally, a test failure! Let's get that div in place:
export const AppointmentsDayView = () =>
<div id="appointmentsDayView"></div>;
  1. Your test should now be passing. Let's move on to the next test. Add the following text, just below the last test in test/Appointment.test.js, still inside the AppointmentsDayView describe block:
it('renders multiple appointments in an ol element', () => {
const today = new Date();
const appointments = [
{ startsAt: today.setHours(12, 0) },
{ startsAt: today.setHours(13, 0) }
];
render(<AppointmentsDayView appointments={appointments} />);
expect(container.querySelector('ol')).not.toBeNull();
expect(
container.querySelector('ol').children
).toHaveLength(2);
});
  1. Run your tests:
expect(received).not.toBeNull()

Received: null

57 | ];
58 | render(<AppointmentsDayView appointments={appointments} />);
> 59 | expect(container.querySelector('ol')).not.toBeNull();
| ^
60 | expect(container.querySelector('ol').children).toHaveLength(2);
61 | });
62 | });

at Object.toBeNull (test/Appointment.test.js:48:47)
In the test, the today constant is defined to be new Date(). Each of the two records then uses this as a kind of "base" date to work its own time off. Whenever we're dealing with dates, it's important that we base all events on the same moment in time, rather than asking the system for the current time more than once. Doing that is a subtle bug waiting to happen.
  1. Let's add the ol element. Remember not to jump ahead; at this point, we just need ol to be there, not including the two items:
export const AppointmentsDayView = () => (
<div id="appointmentsDayView">
<ol />
</div>
);
  1. Run npm test again. The test output is now as follows:
Expected length: 2
Received length: 0
Received object: []

47 | render(<Appointments appointments={appointments} />);
48 | expect(container.querySelector('ol')).not.toBeNull();
> 49 | expect(container.querySelector('ol').children).toHaveLength(2);
| ^
50 | });
51 | });
52 |
  1. Since we've got multiple expectations in this test, the stack trace is essential in highlighting which expectation failed. This time, it's the second expectation: we've got zero children in the ol element but we want two. To fix this, as always, we'll do the simplest thing that will possibly work, as follows:
export const AppointmentsDayView = ({ appointments }) => (
<div id="appointmentsDayView">
<ol>
{appointments.map(() => (
<div />
))}
</ol>
</div>
);
The map function will provide a single argument to the function passed to it. Since we don't use the argument (yet), we don't need to assign it in the function signature—we can just pretend that our function has no arguments instead, hence the empty brackets. Don't worry, we'll need the argument for a subsequent test and we'll add it in then.
  1. If we're being strict, this isn't quite right: ol elements should not have div elements for children. But, that's all we should need to pass the test. We can use the next test to make sure the children are li elements. Let's see what Jest says; run npm test again:
PASS test/Appointment.test.js
Appointment
✓ renders the customer first name (19ms)
✓ renders another customer first name (2ms)
AppointmentsDayView
✓ renders a div with the right id (7ms)
✓ renders multiple appointments in an ol element (16ms)

console.error node_modules/react/cjs/react.development.js:217
Warning: Each child in an array or iterator should have a unique "key" prop.
  1. Our test passed, but we got a warning from React. It's telling us to set a key value on each li element. We can use startsAt as a key:
<ol>
{appointments.map(appointment => (
<div key={appointment.startsAt} />
))}
</ol>
Unfortunately there's no easy way for us test key values in React. To do it, we'd need to rely on internal React properties, which would make our tests at risk of breaking if the React team were to ever change those properties.

The best we can do is set a key to get rid of this warning message. Any value will do: unfortunately we can't use TDD to specify how keys are formed.

In this case, I'd quite like a test that uses the startsAt timestamp for each li key. Let's just imagine that we have that test in place.

Selecting data to view

Let's add in some dynamic behavior to our page. We'll make each of the list items a link that the user can click on to view that appointment.

Thinking through our design a little, there are a few pieces we'll need:

  • A button element within our li
  • An onClick handler that is attached to that button element
  • A component state to record which appointment is currently being viewed

When we test React actions, we do it by observing the consequences of those actions. In this case, we can click on a button and then check that its corresponding appointment is now rendered on screen.

Initial selection of data

Let's start by asserting that each li element has a button element:

  1. First up, let's display a message to the user if there are no appointments scheduled for today. In the AppointmentsDayView describe block, add this test:
it('initially shows a message saying there are no appointments today', () => {
render(<AppointmentsDayView appointments={[]} />);
expect(container.textContent).toMatch(
'There are no appointments scheduled for today.'
);
});
  1. Make that pass by adding in a message at the bottom of rendered output. We don't need a check for an empty appointments array just yet; we'll need another test to triangulate to that:
return (
<div id="appointmentsDayView">
...
<p>There are no appointments scheduled for today.</p>
</div>
);
  1. If there are appointments scheduled, then we start off by showing the first one of the day. We can check for a rendered customer firstName to determine whether the right customer is shown:
it('selects the first appointment by default', () => {
render(<AppointmentsDayView appointments={appointments} />);
expect(container.textContent).toMatch('Ashley');
});
  1. Since we're looking for the customer name, we'll need to make sure that's available in the appointments array. Update it now to include the customer firstName:
  const appointments = [
{
startsAt: today.setHours(12, 0),
customer: { firstName: 'Ashley' }
},
{
startsAt: today.setHours(13, 0),
customer: { firstName: 'Jordan' }
}
];
  1. Let's make that pass by using our Appointment component. Modify the last line of the div component to read as follows:
<div id="appointmentsDayView">
// ... existing code here ...
{appointments.length === 0 ? (
<p>There are no appointments scheduled for today.</p>
) : (
<Appointment {...appointments[0]} />
)}
</div>

Now, we're ready to let the user make a selection.

Adding events to a functional component

We're about to add state to our component. The component will show a button for each appointment. When the button is clicked, the component stores the array index of the appointment that it refers to. To do that, we'll use the useState hook.

Hooks are a feature of React that manage various non-rendering related operations. The useState hook stores data across multiple renders of your function. The call to useState returns you both the current value in storage and a setter function that allows it to be set.

If you're new to hooks, check out the Further learning section at the end of this chapter. Alternatively, you could just follow along and see how much you can pick up just by reading the tests!

Let's start by asserting that each li element has a button element:

  1. Add the following test, just below the last one you added. The second expectation is a little peculiar in that it is checking the type of the button to be button. If you haven't seen this before, it's idiomatic when using button elements to define its role by setting the type attribute, as I'm doing here:
it('has a button element in each li', () => {
render(<AppointmentsDayView appointments={appointments} />);
expect(
container.querySelectorAll('li > button')
).toHaveLength(2);
expect(
container.querySelectorAll('li > button')[0].type
).toEqual('button');
});
We don't need to be pedantic about checking the content or placement of the button element within its parent. For example, this test would pass if we put an empty button child at the end of li. But, thankfully, doing the right thing is just as simple as doing the wrong thing, so we can opt to do the right thing instead. All we need to do to make this pass is wrap the existing content in the new tag.
  1. Make this test pass by modifying the AppointmentsDayView return value, as shown:
<ol>
{appointments.map(appointment => (
<li key={appointment.startsAt}>
<button type="button">
{appointmentTimeOfDay(appointment.startsAt)}
</button>
</li>))}
</ol>;
  1. We can now test what happens when the button is clicked. Back in test/Appointment.test.js, add the following as the next test. This uses the ReactTestUtils.Simulate.click function to perform the click action:
it('renders another appointment when selected', () => {
render(<AppointmentsDayView appointments={appointments} />);
const button = container.querySelectorAll('button')[1];
ReactTestUtils.Simulate.click(button);
expect(container.textContent).toMatch('Jordan');
});
React components respond to what it calls synthetic events. React uses these to mask browser discrepancies in the DOM event model. That means we can't raise standard events that we'd fire through JSDOM. Instead, we use the ReactTestUtils.Simulate object to raise events.
  1. Include the following import at the top of test/Appointment.test.js:
import ReactTestUtils from 'react-dom/test-utils';
  1. Go ahead and run the test:
  ● AppointmentsDayView › renders appointment when selected

expect(received).toMatch(expected)

Expected value to match:
"Jordan"
Received:
"12:0013:00Ashley"

We're getting all of the list content dumped out too, because we've used container.textContent in our expectation rather than something more specific.

At this stage, I'm not too bothered about where the customer name appears on screen. Testing container.textContent is like saying I want this text to appear somewhere, but I don't care where. Later on, we'll see techniques for expecting text in specific places.

There's a lot we now need to get in place in order to make the test pass: we need to introduce state and we need to add the handler. But, first, we'll need to modify our definition to use a block with a return statement:

  1. Set the last test to skip, using it.skip.
We never refactor on red. It's against the rules! But if you're on red, you can cheat a little by rewinding to green by skipping the test that you've just written.

It may seem a little pedantic to do that for the very tiny change we're about to make, but it's good practice.
  1. Wrap the constant definition in curly braces, and then return the existing value. Once you've made this change, run your tests and check you're all green:
export const AppointmentsDayView = ({ appointments }) => {
return (
<div id="appointmentsDayView">
<ol>
{appointments.map(appointment => (
<li key={appointment.startsAt}>
<button type="button">
{appointmentTimeOfDay(appointment)}
</button>
</li>))}
</ol>
<Appointment customer={appointments[0].customer} />
</div>
);
};
  1. Unskip the latest test by changing it.skip to it, and let's get to work on making it pass.
  2. Update the import at the top of the file to pull in the useState function:
import React, { useState } from 'react';
  1. Add the following line above the return statement:
const [selectedAppointment, setSelectedAppointment] = useState(
0
);
  1. We can now use this selectedAppointment rather than hard-coding an index selecting the right appointment. Change the return value to use this new state value when selecting an appointment:
<div id="appointmentsDayView">
...
<Appointment {...appointments[selectedAppointment]} />
</div>
  1. Then, change the map call to include an index in its arguments. Let's just name that i:
{appointments.map((appointment, i) => (
<li key={appointment.startsAt}>
<button type="button">
{appointmentTimeOfDay(appointment.startsAt)}
</button>
</li>
))}
  1. Now call setSelectedAppointment from within the onClick handler on the button element:
<button
type="button"
onClick={() => setSelectedAppointment(i)}>
  1. Run your tests, and you should find they're all green:
PASS test/Appointment.test.js
Appointment
✓ renders the customer first name (18ms)
✓ renders another customer first name (2ms)
AppointmentsDayView
✓ renders a div with the right id (7ms)
✓ renders multiple appointments in an ol element (16ms)
✓ renders each appointment in an li (4ms)
✓ initially shows a message saying there are no appointments today (6ms)
✓ selects the first element by default (2ms)
✓ has a button element in each li (2ms)
✓ renders another appointment when selected (3ms)

Our component is now complete and ready to be used in the rest of our application. That is, once we've built the rest of the application!

Manually testing our changes

The Git tag for this section is entrypoint.

The words manual testing should strike fear into the heart of every TDDer. Manual testing takes up so much time. I usually avoid it if I can. That being said, even if we wanted to manually test, we couldn't as we can't yet run our app. To do that, we'll need to add an entrypoint.

Putting it all together with Webpack

Jest includes Babel, which transpiles all our code when it's run in the test environment. But what about when we're serving our code via our website? Jest won't be able to help us there.

That's where Webpack comes in, and we can introduce it now to help us, do a quick manual test:

  1. Install Webpack using the following command:
npm install --save-dev webpack webpack-cli babel-loader
  1. Add the following to the scripts section of your package.json:
"build": "webpack",
  1. You'll also need to set some configuration for Webpack. Create the webpack.config.js file in your project root directory with the following content:
const path = require("path");
const webpack = require("webpack");

module.exports = {
mode: "development",
module: {
rules: [{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
loader: 'babel-loader'}]}
};

This configuration works for Webpack in development mode. Consult the Webpack documentation for information on setting up production builds.

  1. In your source directory, run the following commands:
mkdir dist
touch dist/index.html
  1. Add the following content to the file you just created:
<!DOCTYPE html>
<html>
<head>
<title>Appointments</title>
</head>
<body>
<div id="root"></div>
<script src="main.js"></script>
</body>
</html>
  1. You're now ready to run the build:
npm run build

You should see a bunch of output like this:

  Asset Size Chunks Chunk Names
main.js 764 KiB main [emitted] main
Entrypoint main = main.js
[./src/Appointment.js] 4.67 KiB {main} [built]
[./src/index.js] 544 bytes {main} [built]
[./src/sampleData.js] 726 bytes {main} [built]
+ 11 hidden modules
  1. Open index.html in your browser and behold your creation:
The following screenshot shows the application once the Exercises are completed, and with added CSS and extended sample data. To include the CSS, you'll need to pull dist/index.html and dist/styles.css from the chapter-2 tag. The sample data can be found in src/sampleData.js, within the same tag. If you're choosing not to complete the Exercises, you can skip to that tag now.

As you can see, we've only got a little part of the way to fully building our application. The first few tests of any application are always the hardest and take the longest to write. We are now over that hurdle, so we'll move quicker from here onward.

Summary

One of the many wonderful things about test-driven development is that it’s teachable. Tests act like a safety harness in our learning: we can build little blocks of understanding, building on top of each other, up and up to ever-greater heights, without fear of falling.

In this chapter, you've learned a lot of the test-driven development experience: the red-green-refactor cycle, triangulation, and Arrange, Act, Assert. You've also learned some design principles such as DRY and YAGNI.

While this is a great start, the journey has only just begun. Coming up next, we'll look at test-driving React forms and building complex user interface designs with our tests acting as scaffold.

Exercises

The Git tag for this section is chapter-1-exercises.
  • Rename Appointment.js and Appointment.test.js to AppointmentsDayView.js and AppointmentsDayView.test.js. While it's fine to include multiple components in one file if they form a hierarchy, you should always name the file after the root component for that hierarchy.
  • Complete the Appointment component by displaying the following fields on the page. You should use a table HTML element to give the data some visual structure. This shouldn't affect how you write your tests:
    • Customer last name, using the lastName field
    • Customer telephone number, using the phoneNumber field
    • Stylist name, using the stylist field
    • Salon service, using the service field
    • Appointment notes, using the notes field
  • Add a heading to Appointment to make it clear which appointment time is being viewed.
  • There is some repeated sample data. We've used sample data in our tests and we also have sampleAppointments in src/sampleData.js, which we used to manually test our application. Do you think it is worth drying this up? If so, why? If not, why not?

Further learning

Left arrow icon Right arrow icon
Download code icon Download Code

Key benefits

  • Learn the TDD process using the React framework
  • Build complex, real-world applications with a pragmatic approach to TDD
  • Use Cucumber for acceptance and BDD testing, bringing TDD to the wider team

Description

Many programmers are aware of TDD but struggle to apply it beyond basic examples. This book teaches how to build complex, real-world applications using Test-Driven Development (TDD). It takes a first principles approach to the TDD process using plain Jest and includes test-driving the integration of libraries including React Router, Redux, and Relay (GraphQL). Readers will practice systematic refactoring while building out their own test framework, gaining a deep understanding of TDD tools and techniques. They will learn how to test-drive features such as client- and server-side form validation, data filtering and searching, navigation and user workflow, undo/redo, animation, LocalStorage access, WebSocket communication, and querying GraphQL endpoints. The book covers refactoring codebases to use the React Router and Redux libraries. via TDD. Redux is explored in depth, with reducers, middleware, sagas, and connected React components. The book also covers acceptance testing using Cucumber and Puppeteer. The book is fully up to date with React 16.9 and has in-depth coverage of hooks and the ‘act’ test helper.

Who is this book for?

The target audience for this book is JavaScript developers who are looking to implement test-driven and behavior-driven approaches for their React applications.

What you will learn

  • Build test-driven applications using React 16.9+ and Jest
  • Build complete web applications using a variety of HTML input elements
  • Understand the different types of test double and when to apply them
  • Test-drive the Integration of libraries such as React Router, Redux, and Relay (GraphQL)
  • Learn when to be pragmatic and how to apply TDD shortcuts
  • Test-drive interaction with browser APIs including fetch and WebSocket
  • Use Cucumber.js and Puppeteer to build BDD-style acceptance tests for your applications
  • Build and test async Redux code using redux-saga and expect-redux
Estimated delivery fee Deliver to Estonia

Premium delivery 7 - 10 business days

€25.95
(Includes tracking information)

Product Details

Country selected
Publication date, Length, Edition, Language, ISBN-13
Publication date : May 03, 2019
Length: 496 pages
Edition : 1st
Language : English
ISBN-13 : 9781789133417
Languages :
Tools :

What do you get with Print?

Product feature icon Instant access to your digital copy whilst your Print order is Shipped
Product feature icon Paperback book shipped to your preferred address
Product feature icon Redeem a companion digital copy on all Print orders
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
Product feature icon AI Assistant (beta) to help accelerate your learning
OR
Modal Close icon
Payment Processing...
tick Completed

Shipping Address

Billing Address

Shipping Methods
Estimated delivery fee Deliver to Estonia

Premium delivery 7 - 10 business days

€25.95
(Includes tracking information)

Product Details

Publication date : May 03, 2019
Length: 496 pages
Edition : 1st
Language : English
ISBN-13 : 9781789133417
Languages :
Tools :

Packt Subscriptions

See our plans and pricing
Modal Close icon
€11.99 billed monthly
Feature tick icon Unlimited access to Packt's library of 6,500+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Simple pricing, no contract
€119.99 billed annually
Feature tick icon Unlimited access to Packt's library of 6,500+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Choose a DRM-free eBook or Video every month to keep
Feature tick icon PLUS own as many other DRM-free eBooks or Videos as you like for just €5 each
Feature tick icon Exclusive print discounts
€169.99 billed in 18 months
Feature tick icon Unlimited access to Packt's library of 6,500+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Choose a DRM-free eBook or Video every month to keep
Feature tick icon PLUS own as many other DRM-free eBooks or Videos as you like for just €5 each
Feature tick icon Exclusive print discounts

Frequently bought together


Stars icon
Total 113.97
Mastering React Test-Driven Development
€43.99
React Design Patterns and Best Practices
€32.99
Hands-On Full-Stack Web Development with GraphQL and React
€36.99
Total 113.97 Stars icon
Banner background image

Table of Contents

15 Chapters
First Steps with Test-Driven Development Chevron down icon Chevron up icon
Test-driving Data Input with React Chevron down icon Chevron up icon
Exploring Test Doubles Chevron down icon Chevron up icon
Creating a User Interface Chevron down icon Chevron up icon
Humanizing Forms Chevron down icon Chevron up icon
Filtering and Searching Data Chevron down icon Chevron up icon
Test-driving React Router Chevron down icon Chevron up icon
Test-driving Redux Chevron down icon Chevron up icon
Test-driving GraphQL Chevron down icon Chevron up icon
Building a Logo Interpreter Chevron down icon Chevron up icon
Adding Animation Chevron down icon Chevron up icon
Working with WebSockets Chevron down icon Chevron up icon
Writing Your First Acceptance Test Chevron down icon Chevron up icon
Adding Features Guided by Acceptance Tests Chevron down icon Chevron up icon
Understanding TDD in the Wider Testing Landscape Chevron down icon Chevron up icon

Customer reviews

Top Reviews
Rating distribution
Full star icon Full star icon Full star icon Full star icon Half star icon 4.6
(12 Ratings)
5 star 75%
4 star 8.3%
3 star 16.7%
2 star 0%
1 star 0%
Filter icon Filter
Top Reviews

Filter reviews by




窪田 昌二 Aug 14, 2022
Full star icon Full star icon Full star icon Full star icon Full star icon 5
このreact本は、reactを知りつくした著者がすごく丁寧にreactの開発手順を書いてくれていると思いました。まだ途中ですが、十分私には役に立っています。もっとも感動したのは、redux-sagaです。最初、全然分からなくて、何故こんなややこしいことするのだろう。非同期でなくてもいいから簡単な方法でまず書いて欲しいと思っていました。しかし、ソースコードを見て段々分かってくるとこれが一番簡単な方法であることが分かりました。redux-sagaについてはネットでもあまり情報がありませんのでこの本のソースコードだけが頼りでした。今はreactがより好きになりました。著者の方、ありがとうございます!
Amazon Verified review Amazon
Alexandre PINON Jan 12, 2021
Full star icon Full star icon Full star icon Full star icon Full star icon 5
Great book with all steps covered. Excellent even for absolute TDD beginners.Also a good book to discover on how to use react associated libraries (redux, graphql, react router).Recommended to learn testing react.May need to supply with some research to better understand some of the libraries used.The best part: exercices and solutions on github
Amazon Verified review Amazon
Valentyn Solovyov Jan 26, 2020
Full star icon Full star icon Full star icon Full star icon Full star icon 5
Being a seasoned react developer I found a lot of new information about testing react applications in all flavors.
Amazon Verified review Amazon
Amazon Customer Apr 12, 2021
Full star icon Full star icon Full star icon Full star icon Full star icon 5
good
Amazon Verified review Amazon
manandearth Aug 12, 2019
Full star icon Full star icon Full star icon Full star icon Full star icon 5
I find Daniel Irvine's approach sound and helpful from the first page.Spending some few months building apps in React and Redux, I felt like I was writing Javascript in the wild ( I saw this term somewhere, I don't remember where...) With the clear steps described in Mastering React Test-Driven Development, I know that I am in good hands. The methods are clearly explained and reasoned.And the good practices introduced ( practically ) are most useful regardless of language or framework.Absolutely fantastic work by a good and experienced developer.
Amazon Verified review Amazon
Get free access to Packt library with over 7500+ books and video courses for 7 days!
Start Free Trial

FAQs

What is the delivery time and cost of print book? Chevron down icon Chevron up icon

Shipping Details

USA:

'

Economy: Delivery to most addresses in the US within 10-15 business days

Premium: Trackable Delivery to most addresses in the US within 3-8 business days

UK:

Economy: Delivery to most addresses in the U.K. within 7-9 business days.
Shipments are not trackable

Premium: Trackable delivery to most addresses in the U.K. within 3-4 business days!
Add one extra business day for deliveries to Northern Ireland and Scottish Highlands and islands

EU:

Premium: Trackable delivery to most EU destinations within 4-9 business days.

Australia:

Economy: Can deliver to P. O. Boxes and private residences.
Trackable service with delivery to addresses in Australia only.
Delivery time ranges from 7-9 business days for VIC and 8-10 business days for Interstate metro
Delivery time is up to 15 business days for remote areas of WA, NT & QLD.

Premium: Delivery to addresses in Australia only
Trackable delivery to most P. O. Boxes and private residences in Australia within 4-5 days based on the distance to a destination following dispatch.

India:

Premium: Delivery to most Indian addresses within 5-6 business days

Rest of the World:

Premium: Countries in the American continent: Trackable delivery to most countries within 4-7 business days

Asia:

Premium: Delivery to most Asian addresses within 5-9 business days

Disclaimer:
All orders received before 5 PM U.K time would start printing from the next business day. So the estimated delivery times start from the next day as well. Orders received after 5 PM U.K time (in our internal systems) on a business day or anytime on the weekend will begin printing the second to next business day. For example, an order placed at 11 AM today will begin printing tomorrow, whereas an order placed at 9 PM tonight will begin printing the day after tomorrow.


Unfortunately, due to several restrictions, we are unable to ship to the following countries:

  1. Afghanistan
  2. American Samoa
  3. Belarus
  4. Brunei Darussalam
  5. Central African Republic
  6. The Democratic Republic of Congo
  7. Eritrea
  8. Guinea-bissau
  9. Iran
  10. Lebanon
  11. Libiya Arab Jamahriya
  12. Somalia
  13. Sudan
  14. Russian Federation
  15. Syrian Arab Republic
  16. Ukraine
  17. Venezuela
What is custom duty/charge? Chevron down icon Chevron up icon

Customs duty are charges levied on goods when they cross international borders. It is a tax that is imposed on imported goods. These duties are charged by special authorities and bodies created by local governments and are meant to protect local industries, economies, and businesses.

Do I have to pay customs charges for the print book order? Chevron down icon Chevron up icon

The orders shipped to the countries that are listed under EU27 will not bear custom charges. They are paid by Packt as part of the order.

List of EU27 countries: www.gov.uk/eu-eea:

A custom duty or localized taxes may be applicable on the shipment and would be charged by the recipient country outside of the EU27 which should be paid by the customer and these duties are not included in the shipping charges been charged on the order.

How do I know my custom duty charges? Chevron down icon Chevron up icon

The amount of duty payable varies greatly depending on the imported goods, the country of origin and several other factors like the total invoice amount or dimensions like weight, and other such criteria applicable in your country.

For example:

  • If you live in Mexico, and the declared value of your ordered items is over $ 50, for you to receive a package, you will have to pay additional import tax of 19% which will be $ 9.50 to the courier service.
  • Whereas if you live in Turkey, and the declared value of your ordered items is over € 22, for you to receive a package, you will have to pay additional import tax of 18% which will be € 3.96 to the courier service.
How can I cancel my order? Chevron down icon Chevron up icon

Cancellation Policy for Published Printed Books:

You can cancel any order within 1 hour of placing the order. Simply contact [email protected] with your order details or payment transaction id. If your order has already started the shipment process, we will do our best to stop it. However, if it is already on the way to you then when you receive it, you can contact us at [email protected] using the returns and refund process.

Please understand that Packt Publishing cannot provide refunds or cancel any order except for the cases described in our Return Policy (i.e. Packt Publishing agrees to replace your printed book because it arrives damaged or material defect in book), Packt Publishing will not accept returns.

What is your returns and refunds policy? Chevron down icon Chevron up icon

Return Policy:

We want you to be happy with your purchase from Packtpub.com. We will not hassle you with returning print books to us. If the print book you receive from us is incorrect, damaged, doesn't work or is unacceptably late, please contact Customer Relations Team on [email protected] with the order number and issue details as explained below:

  1. If you ordered (eBook, Video or Print Book) incorrectly or accidentally, please contact Customer Relations Team on [email protected] within one hour of placing the order and we will replace/refund you the item cost.
  2. Sadly, if your eBook or Video file is faulty or a fault occurs during the eBook or Video being made available to you, i.e. during download then you should contact Customer Relations Team within 14 days of purchase on [email protected] who will be able to resolve this issue for you.
  3. You will have a choice of replacement or refund of the problem items.(damaged, defective or incorrect)
  4. Once Customer Care Team confirms that you will be refunded, you should receive the refund within 10 to 12 working days.
  5. If you are only requesting a refund of one book from a multiple order, then we will refund you the appropriate single item.
  6. Where the items were shipped under a free shipping offer, there will be no shipping costs to refund.

On the off chance your printed book arrives damaged, with book material defect, contact our Customer Relation Team on [email protected] within 14 days of receipt of the book with appropriate evidence of damage and we will work with you to secure a replacement copy, if necessary. Please note that each printed book you order from us is individually made by Packt's professional book-printing partner which is on a print-on-demand basis.

What tax is charged? Chevron down icon Chevron up icon

Currently, no tax is charged on the purchase of any print book (subject to change based on the laws and regulations). A localized VAT fee is charged only to our European and UK customers on eBooks, Video and subscriptions that they buy. GST is charged to Indian customers for eBooks and video purchases.

What payment methods can I use? Chevron down icon Chevron up icon

You can pay with the following card types:

  1. Visa Debit
  2. Visa Credit
  3. MasterCard
  4. PayPal
What is the delivery time and cost of print books? Chevron down icon Chevron up icon

Shipping Details

USA:

'

Economy: Delivery to most addresses in the US within 10-15 business days

Premium: Trackable Delivery to most addresses in the US within 3-8 business days

UK:

Economy: Delivery to most addresses in the U.K. within 7-9 business days.
Shipments are not trackable

Premium: Trackable delivery to most addresses in the U.K. within 3-4 business days!
Add one extra business day for deliveries to Northern Ireland and Scottish Highlands and islands

EU:

Premium: Trackable delivery to most EU destinations within 4-9 business days.

Australia:

Economy: Can deliver to P. O. Boxes and private residences.
Trackable service with delivery to addresses in Australia only.
Delivery time ranges from 7-9 business days for VIC and 8-10 business days for Interstate metro
Delivery time is up to 15 business days for remote areas of WA, NT & QLD.

Premium: Delivery to addresses in Australia only
Trackable delivery to most P. O. Boxes and private residences in Australia within 4-5 days based on the distance to a destination following dispatch.

India:

Premium: Delivery to most Indian addresses within 5-6 business days

Rest of the World:

Premium: Countries in the American continent: Trackable delivery to most countries within 4-7 business days

Asia:

Premium: Delivery to most Asian addresses within 5-9 business days

Disclaimer:
All orders received before 5 PM U.K time would start printing from the next business day. So the estimated delivery times start from the next day as well. Orders received after 5 PM U.K time (in our internal systems) on a business day or anytime on the weekend will begin printing the second to next business day. For example, an order placed at 11 AM today will begin printing tomorrow, whereas an order placed at 9 PM tonight will begin printing the day after tomorrow.


Unfortunately, due to several restrictions, we are unable to ship to the following countries:

  1. Afghanistan
  2. American Samoa
  3. Belarus
  4. Brunei Darussalam
  5. Central African Republic
  6. The Democratic Republic of Congo
  7. Eritrea
  8. Guinea-bissau
  9. Iran
  10. Lebanon
  11. Libiya Arab Jamahriya
  12. Somalia
  13. Sudan
  14. Russian Federation
  15. Syrian Arab Republic
  16. Ukraine
  17. Venezuela