5. About This Exercise
To see outside-in test-driven development in action, let’s walk through creating a few features in a simple front-end application. We’ll build an app for rating dishes at restaurants, called Opinion Ate. We’ll get to experience all parts of the outside-in TDD process, but note that we’ll only get the app started and won’t get anywhere near finishing it.
If you’d like to download the completed project, you can do so from the Opinion Ate React repo. But I would highly encourage you to work through the exercise yourself. Even more so than programming in general, test-driven development requires practice to get into the habit and to really experience the benefits.
A Note on Learning
If you aren’t used to test-driven development, the process can feel slow at first, and it can be tempting to give up. Stick with it through this guide! The value of TDD usually doesn’t click until you’ve gotten a bit of practice. Anything new you learn is going to be slower while you’re learning it. Once you’ve gotten some practice with TDD it’ll be a tool in your tool belt, and then you’ll be in a better position to decide whether and how often to use it.
As is the case with most TDD tutorials, the functionality we’ll be writing here is so simple that it would probably be quicker to write it without tests. In real applications, the time TDD takes is offset by the time you save troubleshooting, tracking down production bugs, restructuring your code, manually testing, and struggling to write tests for code that wasn’t written to be testable. It’s difficult to demonstrate that kind of time savings in an exercise, but consider this: how much time do you spend on all those problems? How much more enjoyable would your development process be if you could significantly reduce them? Once you’ve learned TDD you can try it out on your real projects and see if you see improvements.
Tech Stack
Our application is going to follow a common architectural pattern for front-end apps involving three layers:
- User interface: the components that make up the screens. Implemented in React.
- State management: stores application data and provides operations to work with it. Implemented in Redux.
- API client: provides access to a web service. Implemented using Axios.
If you use a different front-end library or any other libraries, don’t worry: the testing principles and practices in this book apply to any front-end application. Go through the exercise, and afterward you’ll be able to apply what you learn to your stack of choice.
Here’s the full stack of libraries we’ll use for our React application:
Build Tooling: Create React App
Create React App allows running our application locally and building it for production. Depending on your production needs you might or might not want a more flexible build tool, like a custom webpack config or Parcel. This tutorial doesn’t get into build configuration, though, so Create React App will work fine, and should be familiar to many readers.
State Management: Redux
Redux is a state management library that’s widely used in the React ecosystem. It used to be the go-to state management library for real-world React applications, but with the release of React’s Context API and the useReducer() hook it isn’t used quite as widely. Redux is now more likely to be used only in cases where you have fairly complex data structures that need to be widely shared throughout the application—which some would argue was the intended use in the first place.
The reason we’re using Redux for this exercise is because it provides a strong boundary between the UI layer and the data layer. If you use React’s built-in state APIs for your data layer it tends to be coupled to components and harder to test in isolation; doing so takes work, creativity, and discipline. By contrast, when you use Redux with React in the idiomatic way you get an unmistakable separation between the UI layer and the data layer for free. Redux-Thunk actions aren’t impacted by the React render cycle with its challenges to asynchronous testing; they are normal JavaScript async functions and can be easily tested as such.
Once you’ve seen the benefits of keeping your data layer separate from React, you’ll have a goal to shoot for with any data layer approach you use.
State Management Asynchrony: Redux Thunk
Redux Thunk is the recommended way to add asynchrony to a Redux data layer for most projects. It works directly with JavaScript’s built-in promises, so this makes it a natural fit for most JavaScript developers.
HTTP Client: Axios
Axios provides a nice simple interface for making web service requests. The browser’s built-in fetch() function is close, but Axios removes some repetitive parts and improves on the API.
UI Components: MUI
Agile development is all about minimizing unnecessary work. For side projects, internally-facing systems, and MVPs, unless visual design is your passion you may be better off using an off-the-shelf component library. Plus, with a thorough test suite like the one we’ll write, you can always refactor to a new visual design with confidence that you haven’t broken any functionality. For this tutorial we’ll go with MUI, a popular React implementation of Google’s Material Design.
Test Runner: Jest
Jest has one of the most popular JavaScript test runners for a number of years, especially in the React ecosystem. It includes everything you need out of the box for testing plain JavaScript code, including the ability to create test doubles.
Component Tests: React Testing Library
React Testing Library (RTL) will help us write component tests. It’s designed around testing the interface instead of the implementation, which aligns with the testing philosophy we’ll take in this book: that way our tests are less likely to break as the application changes.
End-to-End Tests: Cypress
Cypress is an end-to-end testing tool that was written with test-driven development in mind. Because it runs in the same browser context as your front-end app, it has insight into the event loop and network requests, reducing flake and allowing easy request stubbing. If you’ve had a bad experience with other browser automation tools in the past, Cypress will convince you that E2E tests can be valuable and enjoyable.
In addition to E2E tests, Cypress has been building component testing functionality, to meet some of the same needs as RTL. We haven’t used it for this book because it’s in beta as of the time of this writing and has less broad adoption than RTL. But its approach has some interesting benefits, and we’ll be keeping an eye on it as it develops.
Continuous Integration: GitHub Actions
GitHub is extremely popular for source control, and it has a CI service built in as well: GitHub Actions. There are other great CI options too, but the GitHub integration means that all we need to do is add an Actions config file and we’re set to run our tests on every pull request.
Deployment: Netlify
For deploying front-end applications there’s no service simpler than Netlify. Just choose your repo and Netlify will automatically configure your build process, build your app, and deploy it. We’ll only use the most basic Netlify features in this tutorial, but it also has features you’ll need to take your app to production, such as adding a custom domain with an automatically-provisioned SSL certificate.
What’s Next
Now that we’ve reviewed the tech stack we’ll be using, it’s time to get our app set up. In the next chapter we’ll create our application and the environment it runs in, and we’ll do some setup for our development process.
There’s More!
You’ve reached the end of the free sample of Outside-In React Development.
To read the rest of this exercise, buy the full book here: