Imagine you've spent weeks building an application, and it's now live and working well. Users are happy with it. Then, a few months later, your boss asks for some new features. You dive into the project and start writing code, but suddenly, you begin to notice a lot of errors popping up. Fixing them takes a considerable amount of time because you have to remember what each piece of code does and where it fits. This is where tests in your can make a big difference. Writing tests gives you the confidence to make changes to your code without worrying. code In this comprehensive guide, we'll explore Test-Driven Development (TDD) in the context of building a React.js application. We'll focus on the fundamentals of testing a React application. We'll create our own React application using the basic building blocks and even develop our own testing helper functions. Building a React Application from Scratch While is great for quickly setting up a React project. In this case, we won't need all the boilerplate that comes with it. We want to stick to the core React functionalities. This follows the first TDD principle, YAGNI (You Ain’t Gonna Need It), which simply implies that you should only add libraries and code that you need to prevent technical debt in the future. create-react-app Installing Dependencies Before we start, ensure that you have installed. If not, head to for installation instructions. Once that's done, create a folder for our application and follow these steps: npm https://nodejs.org Initialize the project with . npm init -y Open the file and set the property to use . package.json test jest Install Jest by running . npm install --save-dev jest Install React with . npm install --save react react-dom Install Babel for transpiling your code with the following commands: npm install --save-dev @babel/preset-env @babel/preset-react npm install --save-dev @babel/plugin-transform-runtime . npm install --save @babel/runtime Configure Babel to use the plugins you just installed by creating a new file, , with the following content: .babelrc { "presets": ["@babel/env", "@babel/react"], "plugins": ["@babel/transform-runtime"] } Creating Your First Test Let's build an online clothing store that displays a list of products on its page. We'll start by creating the first page, the product page. It shows details about a product and will be a React component named . The first step in the TDD cycle is to write a failing test. Product We create a test file in with the following content: test/Product.test.js describe("Product", () => { it("renders the title", () => { }); }) In the above code, the keyword defines a test suite, which is simply a set of tests with a given name. The name could be a React component, a function, or a module. The function defines a single test. The first argument is the description of the test and should start with a present-tense verb to make it read in plain English. For example, the test above reads as "Product renders the title." describe it and , are required and available in the global namespace when you run , so there is no need to explicitly import them. Note: All Jest functions, such as describe it npm run test When we run , the test will pass because empty tests always pass. Let's add an expectation to our test: npm run test describe("Product", () => { it("renders the title", () => { expect(document.body.textContent).toContain("iPhone 14 Pro") }); }) The function compares an expected value against a received value. In our case, the expected value is "iPhone 14 Pro," and the actual value is whatever is inside . The expectation will pass only if contains the text "iPhone 14 Pro." expect document.body.textContent document.body.textContent When we run the test, we get an error message indicating that Jest is not able to access and suggests installing for our test environment. document jsdom Note: A test environment is a piece of code that runs before and after your test suite. In this instance, the jsdom environment sets globals and document objects and turns Node.js into a browser-like environment. Let's go ahead and install jsdom: npm install --save-dev jest-environment-jsdom One more thing: let's update and tell it to use jsdom as our test environment: package.json { ..., "jest": { "testEnvironment": "jsdom" } } Now, run the test again, and you will see a different error, which is typical of a failing test. This error message can be divided into four parts: The name of the failing test. The expected answer. The actual answer. The location in the source where the error occurred. This is expected since we haven't written any production code yet. To make this test pass, we need to render our component into a React container, just as React works in production. Product To render a component in React, follow these steps: Create a container. Make the container the root. Attach our component to the root. This can be done in one line as follows: ... ReactDOM.createRoot(container).render(component) ... To achieve the same in our test, do the following: Create the container: const container = document.createElement('div'); document.body.appendChild(container); We create the container element and append it to the document. This is necessary because some of the events are only accessible if the element is part of the document tree. Create the component: ... const product = { title: "iPhone 14 Pro", } const component = <Product product={product} /> ... When you put it all together, we have: describe("Product", () => { it("renders the title", () => { const container = document.createElement('div'); document.body.appendChild(container); const product = { title: "iPhone 14 Pro", } const component = <Product product={product} /> ReactDOM.createRoot(container).render(component) expect(document.body.textContent).toContain("iPhone 14 Pro") }); }) We need to include the two standard React imports at the top since we are using ReactDOM and JSX: import React from "react"; import ReactDOM from "react-dom/client"; When we run the test as it is now, we get a because is not defined. To make this test pass, we need to create a component and import it into our test. ReferenceError Product Product Create a file, and inside it, enter the following: Product.jsx export const Product = () => {}; Run the test, and we get a new error This new error suggests that the string we expected is different from what we received. Let's update our component to fix the test error: export const Product = () => "iPhone 14 Pro"; When we run the test, we get the same error, which is due to the asynchronous nature of React 18's function. This causes the expectation to run before the DOM is modified. render We can fix this by using the helper function provided by ReactDOM, which ensures the DOM is modified before other code is executed, making our expectation to execute only after the DOM is modified. act We import as follows: act import { act } from "react-dom/test-utils"; We also have to update the property in the : jest package.json { ..., "jest": { "testEnvironment": "jsdom", "globals": { "IS_REACT_ACT_ENVIRONMENT": true } } } Now, update the line that renders the component to be: ... act(() => ReactDOM.createRoot(container).render(component) ) ... Run the test again, and you will finally see our test passing with no errors. Expanding the Test The above test will pass as long as we expect the content of the body to have "iPhone 14 Pro". However, this is not our desired outcome. To make the test pass for other title values, we introduce a prop to our component. Now, our component should look like this: export const Product = ({ product }) => <p>{product.title}</p>; Let's add another test to the suite: it("renders another the title", () => { const container = document.createElement("div"); document.body.appendChild(container); const product = { title: "Samsung", }; const component = <Product product={product} />; act(() => ReactDOM.createRoot(container).render(component)); console.log(document.body.textContent); expect(document.body.textContent).toContain("Samsung"); }); When we run our test, it should pass. However, there is a small issue. The test is passing because of the way the works, but when we inspect the in the second test, we see something strange. toContain textContent When we run the code above, we get: This indicates that the components are not independent. The document is not being cleared between renders, and we don't want that to happen. We want each unit of the test to be separate from the other. To fix that, we replace with . appendChild replaceChildren Run the test again, and we should be good to go. Conclusion In this article, we've explored the in the context of building a React.js application. We've gone through the process of setting up a basic React application, writing our first failing test, and making it pass by creating the necessary React component and utilizing the function to ensure asynchronous code is handled correctly. fundamental principles of Test-Driven Development (TDD) act By following these TDD practices, you can build more reliable and maintainable React applications that are easier to modify and extend in the future. In the next post in this series, we will refactor the test by extracting the repetitive code.