Test-Driven Development in React Application
The detailed guide on how to apply the TDD methodology

Testing is a very important part of our life. Testing gives you confidence about things. And now you are in the kitchen making a dressing for a salad. You mix up all of the ingredients, and you know their taste more or less. The result of your process will be a salad with souse. Before putting souse into the salad, you possibly want to check — is it salty, sweet, or spicy enough? And in the end, you spice up a salad with dressing.
Trying to understand how it is before accomplishing the end of the process is testing. We test food before serving and the strength of the components before building the bridge. We test the medications before sending them to pharmacies for buyers. NASA made many tests before launching the rocket. A dozen examples could be given here, but the idea of testing is everywhere. With coding, it’s the same.
Writing the code in our projects is like a snowball as it grows — the chance of getting an error and crushed in the end is very high. Also, other developers get onto the project, and the chance of breaking something is higher if there are no tests. When something in the service goes wrong — the business spends time and money fixing it. So, it’s better to have a prediction of potential mistakes by tests.
Reading “Clean Code” by Robert C. Martin, I thought it is very difficult to think about tests first and then create a component. But after some training and the techniques described below, I feel more confident with TDD than ever.
Here you will learn how to use tests for React applications and testing behavior in general. You will see the difference between types of tests, where to use them, and when. Be ready for a lot of coding.
Before starting to write tests, I’d like to highlight three important concepts:
- DDD — Domain-Driven Development
- BDD — Behaviour-Driven Development
- TDD — Test-Driven Development
What Is DD?
DD means Driven Development. This means development is based on some behavior. Take a look at the diagram below:

DDD
This is the correct interpretation of a business idea in code. It’s like the skill of writing enterprise code; the code produced is based on the business idea. It includes such crucial aspects as ubiquitous language, which is a unique communication language between the business and the development sides. On the other hand, Model-Driven Design is based on a model and some patterns for development.
BDD
This means improving communication between the development team and the business. This concept branched from TDD; it’s like a variant or extension of it. BDD is a description of the behavior, and it’s good for integration testing and e2e.
TDD
It’s a development based on testing. TDD believes that you should write tests before writing the code, so you can see if it will fail. And only after the written test do you start writing the functionality for the written test and figuring out the components to satisfy the tests. It’s very good for unit testing.
Tests
- Unit testing — it’s a type of testing when you test one element (or entity), without any binding (wiring) with other elements.
- Integration tests — it’s about rendering logic when React rendered something to the DOM. It looks at interrelated functions, enumeration, some logic dependency functions, and so on.
- E2E testing — when we test how users interact with interfaces. It focuses on the user's behavior with the interface at the end of rendering. There is the browser and created user who walks through an interface.
Application
On the schema, you can see that the application is very simple. In the end, you can find the link to the GitHub repository. But for now, let’s focus on building the application following the TDD methodology. We’re not gonna implement all of the components. It’s important to understand only how to use TDD in React.

In the beginning, it is very important to understand what we’re gonna build and determine what components we need to implement this application. In my explanation, we’re gonna start with the simple ones.
The idea of the application is to render the grid of cards requested from a fake API. Also, we have to be able to add a new card by clicking on the Add
button, and the Clear
button has to remove all of the cards from the grid. And finally, in the Footer
, we show the total number of cards. Each card we have to be able to be opened or removed. I was using create-react-app
.
After installing packages, let’s struct our folders. Each component and view folder has to have index.js
as a file.
Routing in the application has to have the following routes:
/ -> Home Page
/cards -> Cards Grid Page
/card/:id -> Card View
/about -> About View
/* -> Not Found View
Tests
While we described the application in technical language, we already called some components. Each component has to be independent of tests. We have to be able to test components without side dependencies. We will start writing our components from views. We need them to render the App
itself and test routing.
└── views
├── AboutView
├── CardView
├── CardsView
└── HomeView
In the folder, src/views/
, let’s put index.js. <VIEW_NAME>.spec.js
in each file. For example, AboutView.spec.js
and then open it.
Yes, we’re throwing the Error('Not implemented')
and you have to get used to it because the most important part is the description. We have to describe what we gonna test in each it
and what we expect from each test for that component. Now add the following to index.js
:
And back to your test file. We gonna write the test exactly to our expectations from this component. What are we expecting? That after rendering, we’re gonna save a snapshot and About View
title.
Jest helps us save a snapshot of our structure of components to disk and, on subsequent test runs, compare new snapshots with previously saved ones. A snapshot, in this case, is just a textual representation of the data structure. The first time a test snapshot fires, it will write the result of the component’s textual representation to disk. The test will pass and be recorded as a snapshot.
The next time it manipulates the component, the test will fail because there will be a difference in the data written to the snapshot. Why it’s important? To compare and prevent unwanted elements in the component. It is very important to check the difference in snapshots. The snapshot test will be crushed by default if an undefined value is inside. Back to index.js
and adapt our view component exactly for the test.
Do the same for all other views. Write a test and then implementation for your component, function, util, hook, etc. This is the main concept of TDD.
Routes
As you know, we have Routes
in our app that will render our views. In the folder, src/Routes
, create index.js
and Routes.spec.js
and then add this to the file:
By it
, we described exactly what we expected from the Routes
. Let’s add to first in our HomeView
expectation. It’s important to understand what we are expecting from this component.
The first test tells us that HomeView
has to be by route /
. As you can see, we mocked it just to avoid unexpected imports from view components. We isolated by our test implementation. Let’s adapt our Routes
for that test. No more, no less.
Run your test. The result has to be with fallen tests except only the first one related to HomeView
.

One by one, it
adds tests for other views.
And adapt the component for each described test.
App Component
Time to bind it with the main App
component. Go to the App
folder and create files: index.js, App.spec.js
. Remember that App
has to have Routes
. Let’s test it.
Open App.spec.js
, and describe the app test as we did before with throw Error("Not implemented");
.
Of course, we don’t have any App
component yet, but we already understand that in the component, we’re gonna have Routes
. Let’s create the App
component. I am using styled-components
.
Now, let’s run our test for App
. You will see that the snapshot will be created and the test passed.
Header
Time to have some components for our MainLayout
. Header
has title
and the two buttons Add
and Clear
. Let’s start with tests. As usual, with Test not implemented
. Only the first test will be ready for our component.
And then let’s create our Header
exactly for the written test. Only satisfy the test; nothing else.
Then run tests. Two first test has to be passed. OK, let’s add additionally other tests to Header
one by one and improve for each test component.
And after adding buttons to the Header
component — update your snapshot.
As you can see, we are going step by step. The test is first, and then we do the implementation to satisfy that test. Let’s add two additional tests for the Header
, as shown below:
And, of course, adapt the component for those tests. We have to be sure that our functions have been called.
Footer
This component is much simpler, but we gonna render some cards in it. You can see what it looks like above. First, create a test called Footer.spec.js
.
And adapt for each case of testing the component. Almost the same as in the header but much simpler.
Hooks
Yes, we have to test hooks as well in the same approach. Let’s assume we will have some queries to request cards. We have a link for that request, and the expected result in data has to be an array with id
and title
.
For HTTP requests, we’re gonna use axios
. First of all, we have to mock all of this. Let’s go to src/hooks
and create a folder useApi
. There is going to be index.js
and useApi.spec.js
, and in the beginning, let’s add these mocks there:
OK, no. We can describe what we expect from our hook.
This part is already familiar, and we must adapt our hook for that test.
Let’s describe in the test the expectation of behavior for the hook. In this case, mockImplementation
helps to return the promised fake data in each test independently.
We are waiting for changes in a specific field in each of those tests. Why do we have it? Because the hook will be used in a component with a life circle.
Reducer
Absolutely the same approach could be for reducer in context. Let’s imagine that our application has a context, and we’re gonna use some actions right from there with the help of a reducer. Remember that we use reducer with initState
within useReducer
hook:
export const initialState = { cards: [] };const [state, dispatch] = useReducer(reducer, initialState);
So, let’s describe our expectations from that reducer. Here’s the code:
And, of course, adapt our reducer for that tests one by one.
Now, update the test for an additional description of reducer behavior.
And again, adapt reducer for all written tests.
Other Components
Now, try to check other components from the repository and write your own tests. There is Card
, Cards
, MainLayout
with Header
and Footer
. First, create tests and then component implementation.
Conclusion
I just want to show you how to use TDD for your applications. In the beginning, it feels annoying, but you save a lot of time in the end. As a result, you’ll receive the fully tested independent component, which will be clear for other developers if they need to do some updates there.
And in general, TDD provides stability, solidity, and proven solutions that have to be a cornerstone for any enterprise application.
Resources
GitHub repository: https://github.com/antonkalik/tdd-react-example
Want to Connect?I'll be glad to keep in touch through Twitter.It's always a pleasure to receive any suggestions and comments related to the topic. Feel free to ask any questions.Thank you!