49,25 €
Implement TDD for your React applications using Jest, React Router, Redux, and GraphQL/Relay. Learn BDD and end-to-end acceptance testing with CucumberJS and Puppeteer.
Key Features
Book 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.
What you will learn
Who this book is 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.
Das E-Book können Sie in Legimi-Apps oder einer beliebigen App lesen, die das folgende Format unterstützen:
Seitenzahl: 511
Veröffentlichungsjahr: 2019
Copyright © 2019 Packt Publishing
All rights reserved. No part of this book may be reproduced, stored in a retrieval system, or transmitted in any form or by any means, without the prior written permission of the publisher, except in the case of brief quotations embedded in critical articles or reviews.
Every effort has been made in the preparation of this book to ensure the accuracy of the information presented. However, the information contained in this book is sold without warranty, either express or implied. Neither the author, nor Packt Publishing or its dealers and distributors, will be held liable for any damages caused or alleged to have been caused directly or indirectly by this book.
Packt Publishing has endeavored to provide trademark information about all of the companies and products mentioned in this book by the appropriate use of capitals. However, Packt Publishing cannot guarantee the accuracy of this information.
Commissioning Editor: Amarabha BanerjeeAcquisition Editor:Trusha ShriyanContent Development Editor:Keagan CarneiroTechnical Editor: Sachin SunilkumarCopy Editor: Safis EditingProject Coordinator:Kinjal BariProofreader: Safis EditingIndexer:Rekha NairGraphics:Alishon MendonsaProduction Coordinator:Jayalaxmi Raja
First published: May 2019
Production reference: 1020519
Published by Packt Publishing Ltd. Livery Place 35 Livery Street Birmingham B3 2PB, UK.
ISBN 978-1-78913-341-7
www.packtpub.com
Mapt is an online digital library that gives you full access to over 5,000 books and videos, as well as industry leading tools to help you plan your personal development and advance your career. For more information, please visit our website.
Spend less time learning and more time coding with practical eBooks and Videos from over 4,000 industry professionals
Improve your learning with Skill Plans built especially for you
Get a free eBook or video every month
Mapt is fully searchable
Copy and paste, print, and bookmark content
Did you know that Packt offers eBook versions of every book published, with PDF and ePub files available? You can upgrade to the eBook version at www.packt.com and as a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at [email protected] for more details.
At www.packt.com, you can also read a collection of free technical articles, sign up for a range of free newsletters, and receive exclusive discounts and offers on Packt books and eBooks.
Daniel Irvine is a software consultant based in London. He is a member of the European software craft community and works with a variety of languages including C#, Clojure, JavaScript, and Ruby. He’s a mentor and coach for junior developers and runs TDD and XP workshops and courses. When he’s not working, he spends time cooking and practicing yoga. He co-founded and runs the Queer Code London meetup.
Raimo Radczewski is an IT consultant from Berlin, Germany. His journey through the world of software engineering has taught him not just quite a few languages and paradigms, but also how to build and lead engineering teams. He's an advocate for test-driven development and maintains testing tools for the React ecosystem. He organizes SoCraTes Day Berlin and the Global Day Of Coderetreat, two grassroots communities for IT workers who are curious about software crafting and eXtreme Programming. His current focus is founding a tech cooperative to support clients in building a professional and mindful engineering culture. He tweets under @rradczewski.
If you're interested in becoming an author for Packt, please visit authors.packtpub.com and apply today. We have worked with thousands of developers and tech professionals, just like you, to help them share their insight with the global tech community. You can make a general application, apply for a specific hot topic that we are recruiting an author for, or submit your own idea.
Title Page
Copyright and Credits
Mastering React Test-Driven Development
Dedication
About Packt
Why subscribe?
Packt.com
Contributors
About the author
About the reviewer
Packt is searching for authors like you
Preface
Who this book is for
What this book covers
To get the most out of this book
Keeping up with the book's Git history
Getting started before Chapter 1
Working with section tags
Solving the exercises
Debugging when things go wrong
Download the example code files
Conventions used
Understanding code snippets
JavaScript syntax
Prettier
Arrow functions
Object and array destructuring
Directory structure
Get in touch
Reviews
Section 1: First Principles of TDD
First Steps with Test-Driven Development
Technical requirements
Creating a new React project from scratch
Installing NPM
Creating a new Jest project
Commit early and often
Bringing in React and Babel
Displaying data with your first test
Writing a failing test
Writing your first expectation
Rendering React from a test
Make it pass
Backtracking on ourselves
Refactoring your work
Promoting variables
Using a beforeEach block
Extracting methods
Writing great tests
Red, green, refactor
Streamlining your testing process
Rendering lists and detail views
Rendering the list of appointments
Specifying list items
Selecting data to view
Initial selection of data
Adding events to a functional component
Manually testing our changes
Adding an entrypoint
Putting it all together with Webpack
Before you check in...
Summary
Exercises
Further learning
Test-driving Data Input with React
Extracting a test helper
Adding a form element
Extracting a form-finder method
Accepting text input
Extracting an expectation group function
Passing in an existing value
Extracting out a field-finder function
Labeling the field
Checking for null or not
Saving the customer information
Submitting a form with data
Using state instead of props
Duplicating fields
Nesting describe blocks
Generating parameterized tests
Solving a batch of tests
Modifying handleChange to work with multiple fields
Finishing off the form with a submit button
Selecting from a dropdown
Providing options to a dropdown
Utilizing defaultProps to specify real data
Pre-selecting a value
Completing the remaining tests for the select box
Making a choice from radio buttons
Constructing a calendar view
Displaying radio buttons for available appointments
Hiding input controls
Finishing it off
Manually testing your solution
Summary
Exercises
Further learning
Exploring Test Doubles
What is a test double?
Learning to avoid fakes
Submitting forms using spies
Untangling Arrange-Act-Assert
Watching it fail
Making spies reusable
Using a Jest matcher to simplify expectations
Stubbing the fetch API
Replacing global variables with spies
Installing the window.fetch polyfill
Acting on return values with stubs
Acting on the fetch response
Displaying errors to the user
Extracting test helpers
Using Jest to spy and stub
Extracting spy helpers
Using jest.spyOn to spy on module mocks
Drying up DOM queries
Extracting container.querySelectorAll
Drying up DOM events
Summary
Exercises
Further learning
Creating a User Interface
Fetching data on load with useEffect
Stubbing exported constants
Using props within useEffect
Passing customer data through to AppointmentForm
Passing through props to the child component
Working with the shallow renderer
Understanding the importance of spiking
Building shallow renderer helpers
Listing element children
Encapsulating render output to dry up tests
Building a new root component
Summary
Further learning
Section 2: Building a Single-Page Application
Humanizing Forms
Performing client-side validation
Submitting the form
Extracting non-React functionality into a new module
Handling server errors
Indicating that the form has been submitted
Refactoring long methods
Summary
Exercises
Further learning
Filtering and Searching Data
Displaying tabular data fetched from an endpoint
Paging through a large data set
Adding a next page button
Adding a previous page button
Filtering data
Refactoring to simplify component design
Adding table row actions
Specifying the render prop in App
Summary
Exercises
Test-driving React Router
General rules for test-driving React Router
Using shallow rendering for the simplest results
Passing React Router props down through your components
Avoiding withRouter
Building a root component
Using the Router Switch component
Testing the default route
Invoking render functions and inspecting their properties
Changing location using history.push
Using the location query string to store component state
Replacing onClick handlers with Link components
Using a parent component to convert a query string to props
Replacing onChange handlers with history.push
Summary
Exercises
Further learning
Test-driving Redux
Prerequisites
Test-driving a Redux saga
Designing the state object
Scaffolding the saga and reducer
Scaffolding a reducer
Setting up an entrypoint
Making asynchronous requests with sagas
Completing the reducer
Pulling out generator functions for reducer actions
Switching out component state for Redux state
Building a helper function to render with store
Submitting a React form by dispatching a Redux action
Protecting against silent breakages
Shifting workflow to Redux
Stubbing out components built with useMemo
Navigating router history in a Redux saga
Separating Redux connection from presentation
Summary
Exercises
Further learning
Test-driving GraphQL
Installing Relay
Testing the Relay environment
Building the GraphQL reducer
Building the CustomerHistory component
Tying it together in App
Compiling Relay queries
Summary
Exercises
Further learning
Section 3: Interactivity
Building a Logo Interpreter
Studying the Spec Logo user interface
Looking through the codebase
Undoing and redoing user actions in Redux
Building the reducer
Setting the initial state
Handling the undo action
Handling the redo action
Attaching the new reducer
Building buttons
Saving to LocalStorage via Redux middleware
Building middleware
Changing keyboard focus
Writing the reducer
Adding the reducer to the store
Focusing the prompt
Requesting focus in other components
Summary
Further learning
Adding Animation
Isolating components for animation
Designing the component
Extracting out StaticLines
Building an AnimatedLine component
Animating with requestAnimationFrame
Drawing lines
Cleaning up after useEffect
Rotating the turtle
Summary
Exercises
Working with WebSockets
Designing a WebSocket interaction
The new UI elements
Splitting apart the saga
Test-driving a WebSocket connection
Streaming events with redux-saga
Updating the app
Summary
Exercises
Further learning
Section 4: Acceptance Testing with BDD
Writing Your First Acceptance Test
Integrating Cucumber and Puppeteer into your code base
Writing your first Cucumber test
Using data tables to perform setup
Summary
Adding Features Guided by Acceptance Tests
Adding acceptance tests for a dialog box
Fixing acceptance tests by test-driving production code
Adding a dialog box
Updating sagas to reset or replay state
Adding better wait support
Alerting when the animation is complete
Updating step definitions to use waitForSelector
Exercises
Summary
Understanding TDD in the Wider Testing Landscape
Test-driven development as a testing technique
Best practices for your unit tests
Improving your technique
Manual testing
Demonstrating software
Testing the whole product
Exploratory testing
Debugging in the browser
Automated testing
Integration tests
Acceptance tests
Property-based and generative testing
Snapshot testing
Canary testing
Not testing at all
When quality doesn't matter
Spiking and deleting code
Summary
Further learning
Other Books You May Enjoy
Leave a review - let other readers know what you think
This is a book about dogma. My dogma. It is a set of principles, practices, and rituals that I have found to be extremely beneficial when building React applications. I try to apply these ideas in my daily work, and I believe in them so much that I take every opportunity to teach others about them. That's why I've written this book: to show you the ideas that have helped me be successful in my own career.
As with any dogma, you are free to make your own mind up about it. There are people who will dislike everything about this book. There are those who will love everything about this book. Yet more people will absorb some things and forget others. All of these are fine. The only thing I ask is that you maintain an open mind while you follow along, and prepare to have your own dogmas challenged.
Test-driven development (TDD) did not originate in the JavaScript community. However, it is perfectly possible to test-drive JavaScript code. And although TDD is not common in the React community, there's no reason why it shouldn't be. In fact, React as a UI platform is a much better fit for TDD than older UI platforms, due to its elegant model of functional components and state.
So what is TDD, and why should you use it? Test-driven development is a process for writing software that involves writing tests, or specifications, before writing any code. Its practitioners follow it because they believe that it helps them build and design higher-quality software with longer life spans, at a lower cost. They believe it offers a mechanism for communicating about design and specification that also doubles up as a rock-solid regression suite. There isn't much empirical data available that proves any of that to be true, so the best you can do is try it out yourself and make your own mind up.
Perhaps most importantly for me, I find that TDD removes the fear of making changes to my software, and that this makes my working days much less stressful than they used to be. I don't worry about introducing bugs or regressions into my work, because the tests protect me from that.
TDD is often taught with 'toy' examples: todo lists, temperature converters, Tic Tac Toe, and so on. This book teaches two real-world applications. Often, the tests get hairy. We will hit many challenging scenarios and come up with solutions for all of them. There are over 450 tests contained within this book, and every one will teach you something.
So, before we begin, a few words of advice.
This is a book about first principles. I believe that learning TDD is about understanding the process in exceptional detail. For that reason, we do not use Enzyme or react-testing-library. Instead, we build our own test helpers. Doing so is not very complicated. The benefit of doing so is a deeper understanding and awareness of what those testing libraries are doing for you. I am not suggesting that you shouldn't use these tools in your daily work—I use them myself—but I am suggesting that going without them is a worthwhile adventure.
This book uses React hooks. These are a new feature in version 16.8, and we also make use of the act function, which became usable in version 16.9. There are no class components in this book. I believe that we should embrace hooks because functional components using hooks are simpler than class components. I embraced hooks during the process of writing this book, which originally started out as a book with class components. Halfway through, we decided to scrap classes entirely and instead, focus on the future.
On that topic, the JavaScript and React landscape changes at such a pace that I can't claim that this book will remain 'current' for very long. That is another reason why I use a first-principles approach. My hope is that when things do change, you'll still be able to use this book and apply what you've learned to those new scenarios.
There are a variety of themes that run throughout the book. The theme of first principles is one I've already mentioned. Another is systematic refactoring, which can come across as rather laborious, but is a cornerstone of TDD and other good design practices. I have provided many examples of that within these pages, but for brevity, I sometimes jump straight to a 'post-refactored' solution. For example, I often choose to extract methods before they are written, whereas in the real world, I would usually write methods inline and only extract when the containing method (or test) becomes too long.
Yet another theme is that of cheating, which you won't find mentioned in many TDD books. It's an acknowledgment that TDD is really a scaffold around which you can build your own rules. Once you've learned and practiced the strict version of TDD for a while, you can learn what cheats you can use to cut corners. What tests won't provide much value in the long run? How can you speed up repetitive tests? So, a cheat is almost like saying you cut a corner in a way that wouldn't be obvious to an observer if they came to look at your code tomorrow. Maybe, for example, you implement three tests at once, rather than one at a time.
Finally, do not for a second think that I wrote this book in a linear sequence from start to finish, or that I knew exactly what order to write the tests. It took a great deal of spiking, trial and error, and making horrendous mistakes before I ended up with the text you have before you. Needless to say, I am now an expert with git rebase.
If you're a React programmer, this book is for you. I aim to show you how TDD can improve your work.
If you're already knowledgeable with TDD, I hope there's still a lot you can learn from comparing your own process with mine.
If you don't know already know React, you will benefit from spending some time running through the Getting Started guide on the React website. That being said, TDD is a wonderful platform for explaining new technologies, and it's entirely plausible that you'll be able to pick up React simply by following this book.
This book covers in-depth usage of React hooks, which are very new at the time of writing. If you're a React developer and hoping to learn how to use React without classes, then you will indeed learn that by reading this book.
Chapter 1, First Steps with Test-Driven Development, introduces Jest and the test-driven development cycle. We use them to build a rendering of customer information on a page.
Chapter 2, Test-driving Data Input with React, covers using React component state to manage the display and saving of forms.
Chapter 3, Exploring Test Doubles, introduces various types of test double that are necessary for testing collaborating objects. The collaborator we use in this chapter is the browser fetch API to send and receive data from our application backend.
Chapter 4, Creating a User Interface, ties everything with a root component that threads together a user journey.
Chapter 5, Humanizing Forms, continues with form building by looking at dealing with client- and server-side validation errors, and adding an indicator to show that data is being submitted.
Chapter 6, Filtering and Searching Data, shows building a search component with some complex interaction requirements, in addition to complex fetch request requirements.
Chapter 7, Test-driving React Router, introduces the React Router library to simplify navigation with our user journeys.
Chapter 8, Test-driving Redux, introduces Redux into our application in an effort to simplify our components and evolve our application architecture into something that will support larger use cases.
Chapter 9, Test-driving GraphQL, introduces the GraphQL library to communicate with a GraphQL endpoint that's provided by our application backend.
Chapter 10, Building a Logo Interpreter, introduces a fun application that we begin to explore by building out features across both React components and Redux middleware: undo/redo, persisting state across browser sessions with LocalStorage API, and programmatically managing field focus.
Chapter 11, Adding Animation, covers adding animations to our application using the browser requestAnimationFrame API, all with a test-driven approach.
Chapter 12, Working with WebSockets, adds support for WebSocket communication with our application backend.
Chapter 13, Writing Your First Acceptance Test, introduces CucumberJS and Puppeteer, which we use to build acceptance tests for existing functionality.
Chapter 14, Adding Features Guided by Acceptance Tests, integrates acceptance testing into our development process by first building acceptance tests, before dropping down to unit tests.
Chapter 15, Understanding TDD in the Wider Testing Landscape, finishes the book by looking at how what we've learned fits in with other test and quality practices, and provides some suggestions about where to go from here.
There are two ways to read this book.
The first is to use it as a reference when you are faced with specific testing challenges. Use the index to find what you're after and move to that page.
The second, and the one I'd recommend starting with, is to follow the walk-throughs step by step, building your own code base as you go along. In this section, I'll detail how to do that.
You will need to be at least a little proficient with Git: a basic understanding of the branch, checkout, clone, commit, diff, and merge commands should be sufficient.
This section details all you need to know to work effectively with Git while you're following along with the walk-throughs.
The book has an accompanying GitHub repository that contains all of the walk-throughs already implemented in a series of commits. You should clone this to your local development machine as you'll be working within it.
If you have a GitHub account, I suggest you fork the repo so that you can push your work to keep a copy safe. Use the
Fork
button in the top-right hand corner of the GitHub page to do this. The repository is located at
https://github.com/PacktPublishing/Mastering-React-Test-Driven-Development
.
Once forked, you can then clone this locally by going to a terminal window and typing the following command, replacing
<username>
with your GitHub username:
git clone [email protected]:<username>/Mastering-React-Test-Driven-Development.git
You may wish to rename the directory to something shorter. On my machine, I've used the name
react-tdd
.
Change into this directory using the
cd
command.
Issue the command
git checkout tags/starting-point
.
Finally, issue the command
git checkout -b starting-point-mine
to create your own branch from this point.
You're now ready to begin Chapter 1. If the last two commands didn't make any sense, don't panic: I'll explain about tags and branches now.
There are two separate code bases in this book, and they have their own branches: appointments and spec-logo. Chapter 1 to Chapter 9 cover appointments; Chapter 10 to Chapter 14 cover spec-logo. (Chapter 15 doesn't have any code.)
If you were to check out these branches, you'd get the final, completed versions of the code. This is an interesting sneak peak but it's not how you'll get started.
Instead, many sections have a designated tag, so you can skip to the tag and examine the code at that point. If you see a callout like this:
...then you can skip to this tag by issuing the following command:
git checkout tags/animation
Once you've output that command, you will be in the detached head state. If you want to begin making changes at that point, you should create a new branch from the tag and work on that. I'd suggest suffixing the name with -mine so that your branches are clearly distinguishable from tags:
git checkout -b animation-mine
You can then commit to this branch. If you have been following along judiciously within your own branch, then you do not need to check out each tag, since you'll already have all of the same code.
However, sometimes you will see a callout like the one that follows, and that means you will need to check out the new tag:
This type of callout means that the code base now contains additional changes since the last edits covered in the book. It often happens at the start of each chapter when the preceding chapter had exercises, but it also happens when the code base skips ahead with changes that are uninteresting or not related to the content of the book.
When you see this callout, you have two options:
You can choose to check out the new tag and start a new branch, starting afresh. In this case, the instructions are the same as before, except now you'd need a different branch name from your existing branch:
git checkout tags/load-available-time-slots
git checkout -b load-available-time-slots-mine
You can choose to continue working on the branch you have. This could be because you've been creative and made changes that aren't covered in the book (which I fully support). In this case,
git diff
and
git merge
are your friends. You will want to review the changes in the latest tag, and then git merge them in. You may need to handle conflicts:
# to view the differences in the new tag
git diff tags/load-available-time-slots
# to auto-merge those differences into your branch
git merge tags/load-available-time-slots
The second option is not entirely risk free, mainly due to the Exercises section at the end of each chapter.
Almost every chapter has an Exercises section at the end. These exercises are designed to give you ideas for how you continue practicing what you've learned. They have already been solved in the GitHub repository so you can see how I've solved them. The next chapter always starts from the point where I've solved the exercises.
Should you choose to solve the exercises—which I encourage you to do—then the likelihood is that you'll have solved them in a different way than I would have. Unfortunately, this might leave you in merge hell when you begin the next chapter.
If you find yourself in this situation, I suggest you first study the differences between your approach and mine. Think about how they differ and the relative merits of each. (Do not think that mine will be any better than yours.)
Then, ensuring you've committed and successfully stored your code, move to a new tag and a new branch, starting again.
In other words, be pragmatic and don't spend an inordinate amount of time fighting the system. It's better to keep moving and not get stuck or frustrated.
Pro tip: always keep your exercise solutions in a separate commit. When you move on to the next chapter, branch from your pre-Exercises commit and merge in the official exercise solutions instead.
Should you get stuck, or your tests fail in a way that you weren't expecting, feel free to launch the application and see what the console is telling you. Add in console.log statements to help you debug.
The best defense against getting stuck is committing early and often. Any time you have a working feature, commit it!
In addition to the GitHub repository, if you prefer you can download the example code files for this book from your account atwww.packt.com. If you purchased this book elsewhere, you can visitwww.packt.com/supportand register to have the files emailed directly to you.
You can download the code files by following these steps:
Log in or register at
www.packt.com
.
Select the
SUPPORT
tab.
Click on
Code Downloads & Errata
.
Enter the name of the book in the
Search
box and follow the onscreen instructions.
Once the file is downloaded, please make sure that you unzip or extract the folder using the latest version of:
WinRAR/7-Zip for Windows
Zipeg/iZip/UnRarX for Mac
7-Zip/PeaZip for Linux
We also have other code bundles from our rich catalog of books and videos available at https://github.com/PacktPublishing/. Check them out!
There are a number of text conventions used throughout this book.
CodeInText: Indicates code words in text, React component names, test names, directory names, filenames, file extensions, pathnames, dummy URLs, user input, and Twitter handles. Here is an example: "In test/domManipulators.js, add the following new property to the return object of createContainer."
Any command-line input or output is written as follows:
$ mkdir css
$ cd css
Bold: Indicates a new term, an important word, or words that you see onscreen. For example, words in menus or dialog boxes appear in the text like this. Here is an example: "The final test for the Undo button is to check that it dispatches an UNDO action when it is clicked."
As much as possible, the book aims to be consistent with its approach to syntax. There are some choices that may be contentious but I hope that they won’t put you off reading. For example, I use semi-colons throughout. If you’d prefer to not use semi-colons, please feel free to ignore them in your own code.
I have used Prettier to format code samples, and its configuration is set within package.json in each of the appointments and spec-logo projects. Here it is:
"prettier": { "singleQuote": true, "printWidth": 67, "jsxBracketSameLine": true}Feel free to change this to your own configuration and reformat files as you see fit. The line width of 67 characters is particularly short but ensures that, for the most part, code snippets do not suffer from line breaks.
The one place this is not true is with test descriptions:
it('passes from and to times through to appointments when retrieving appointments', async () => {In these cases, although the text is printed over two lines, you should enter it on one line only. If you are copy and pasting code samples from the electronic version of this book, you’ll need to remove the extra line breaks that are inserted by your editor.
Finally, both code bases suffer from a distinct lack of civilized directory structure. I hope this isn’t an issue for you; I just didn’t want to spend time discussing building directories and moving files when it isn’t the focus of the book.
Feedback from our readers is always welcome.
General feedback: If you have questions about any aspect of this book, mention the book title in the subject of your message and email us at [email protected].
Errata: Although we have taken every care to ensure the accuracy of our content, mistakes do happen. If you have found a mistake in this book, we would be grateful if you would report this to us. Please visit www.packt.com/submit-errata, selecting your book, clicking on the Errata Submission Form link, and entering the details.
Piracy: If you come across any illegal copies of our works in any form on the Internet, we would be grateful if you would provide us with the location address or website name. Please contact us at [email protected] with a link to the material.
If you are interested in becoming an author: If there is a topic that you have expertise in and you are interested in either writing or contributing to a book, please visit authors.packtpub.com.
Please leave a review. Once you have read and used this book, why not leave a review on the site that you purchased it from? Potential readers can then see and use your unbiased opinion to make purchase decisions, we at Packt can understand what you think about our products, and our authors can see your feedback on their book. Thank you!
For more information about Packt, please visit packt.com.
This section is an exploration of the principles of test-driven development. You'll test drive the rendering of data and the loading of data from our server, and in the process, you'll build your own test helpers that help to simplify and accelerate your testing. By the end of the section, you'll have a working application.
This section includes the following chapters:
Chapter 1
,
First Steps with Test-Driven Development
Chapter 2
,
Test-driving Data Input with React
Chapter 3
,
Exploring Test Doubles
Chapter 4
,
Creating a User Interface
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
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.
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.
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.
Now that NPM is installed, we can create our project:
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.
Create a new directory using
mkdir appointments
and then change to it using
cd appointments
.
Enter the
npm init
command,
which begins the process of initializing a new NPM project and generating a
package.json
file for you.
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.
Next, you'll be asked for a
test
command, f
or which you should type in
jest
. This will enable you to run tests by using the
npm test
shortcut command.
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.
You can accept the defaults for everything else.
Hit
Enter
on the remaining
questions to finish the initialization process.
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
The second sentence of that command output (You should commit this file) is a good cue for us to commit for the first time.
TDD provides natural breakpoints for you to commit code. If you’re starting out with TDD, I’d recommend committing to source control after every single test. That might seem like overkill for your projects at work, but as you're learning, it can be a very effective tool.
If you've ever watched The Weakest Link, you'll know that contestants can choose to bank their winnings at any time, which decreases their risk of losing money but reduces their earning potential. With git, you can use git add to effectively bank your code. This saves a snapshot of your code but does not commit it. If you make a mess in the next test, you can revert to the last banked state. I tend to do this after every test. And, unlike in The Weakest Link, there's no downside to banking!
Committing early and often simplifies commit messages. If you have just one test in a commit, then you can use the test description as your commit message. No thinking is required.
If you're using git, use the following commands to commit what you’ve done so far:
git init
echo "node_modules" > .gitignore
git add .
git commit -m "Blank project with Jest dependency"
Let's install React. That's actually two packages:
npm install --save react react-dom
React makes heavy use of JavaScriptXML (JSX), which we need Babel to transpile for us. Babel also transpiles our modern ES6 and ES7 constructs for us.
Thankfully, Jest already includes Babel, so we just need to install presets and plugins:
npm install --save-dev @babel/preset-env @babel/preset-reactnpm 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.
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.
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.jsfile 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.
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 testcommand:
> [email protected] test /home/daniel/work/react-tdd/ch1> jestFAIL test/Appointment.test.js● Test suite failed to runYour test suite must contain at least one test. at node_modules/jest/node_modules/jest-cli/build/TestScheduler.js:225:24Test Suites: 1 failed, 1 totalTests: 0 totalSnapshots: 0 totalTime: 0.917sRan 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.
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.
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.textContenttoMatch the stringAshley.
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