Better Programming

Advice for programmers.

Follow publication

Test-Driven Development in React Application

The detailed guide on how to apply the TDD methodology

Anton Kalik
Better Programming
Published in
9 min readOct 17, 2022
TDD in React Application
Thanks for the picture of chiabra from unsplash.com

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:

business side DDD (domain-driven development), then communication layer BDD (behavior-driven development), then tech side TDD (test-driven development)
Driven Development

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.

header — has clear and add buttons. has six cards in the middle. finally, a footer and a counter for the total cards
Application schema

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.

packages for the application

After installing packages, let’s struct our folders. Each component and view folder has to have index.js as a file.

folder structure

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.jsand then open it.

About View Test Beginning

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:

Satisfaction for the first test of About View

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.

Test for About View

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.

About View

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:

TDD with components Router

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.

Routes testing

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.

Routes component

Run your test. The result has to be with fallen tests except only the first one related to HomeView.

Routes component tests result
Passed tests for Routes component

One by one, it adds tests for other views.

Full description of tests for Routing

And adapt the component for each described test.

Routes component update for 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");.

App Test with mock

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.

App Component

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.

Header test scenario

And then let’s create our Header exactly for the written test. Only satisfy the test; nothing else.

Header component

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.

Header test controls

And after adding buttons to the Header component — update your snapshot.

Header adaptation for test

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:

Header tests

And, of course, adapt the component for those tests. We have to be sure that our functions have been called.

Final implementation of Header for tests

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.

Footer tests

And adapt for each case of testing the component. Almost the same as in the header but much simpler.

Footer Component

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:

useApi hook test mocking

OK, no. We can describe what we expect from our hook.

useApi test

This part is already familiar, and we must adapt our hook for that test.

useApi hook adaptation for written 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.

Tests for hook useApi

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.

useApi hook implementation

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:

reducer testing

And, of course, adapt our reducer for that tests one by one.

reducer function

Now, update the test for an additional description of reducer behavior.

reducer test update

And again, adapt reducer for all written tests.

reducer function update

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!

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Anton Kalik
Anton Kalik

Written by Anton Kalik

Senior Software Engineer at Amenitiz

Responses (1)