React Testing for Beginners! βοΈπ


What is Testing?
So, Testing in software development refers to analyzing your code and ensuring that it does not break in any scenarios or edge cases that are difficult to recognize during development. By testing the application, we ensure that it will be completely stable in production.
There are mainly three types of testing:
Unit Testing
In unit testing, we test each section of our application separately.
Integration Testing
In Integration testing as the name suggests we test multiple units or sections of our application by connecting them together as they will in final production mode.
E2E
E2E means end-to-end testing, when the development of the application is completed then this testing is performed.
Testing Library
There are various testing libraries out there but in this article we will learn about the current popular one Vitest, which is used by almost everyone in the industry. Vitest is a fast unit-testing framework built for Vite and designed as a drop-in replacement for Jest. It is optimized for speed and works well with modern frontend frameworks like Vue, React, and Svelte.
Key Features of Vitest
β
Lightning Fast β Uses Vite's native ESM and pre-bundling for faster test execution.
β
Jest-Compatible API β If you're familiar with Jest, switching is easy.
β
Built-in TypeScript Support β No extra configuration needed.
Sample Test
Install Vitest in your project:
npm install -D vitest
Update package.json
scripts:
"scripts": {
"test": "vitest"
}
Run tests:
npm test
Basic Example
import { describe, expect, it } from 'vitest';
describe('Math operations', () => {
it('adds numbers correctly', () => {
expect(1 + 2).toBe(3);
});
});
Vitest provides a Jest-like API with core functions such as describe
, it
(or test
), and expect
to structure and run test cases.
1. describe
β Grouping Tests
The describe
function is used to group related tests together. It helps organize test suites logically.
Example:
import { describe, it, expect } from 'vitest';
describe('Math operations', () => {
it('adds numbers correctly', () => {
expect(1 + 2).toBe(3);
});
it('multiplies numbers correctly', () => {
expect(2 * 3).toBe(6);
});
});
π‘ Everything inside describe()
is considered a test suite.
2. it
β Defines a Single Test Case
it
(or test
) is used to define an individual test case.
it('checks if 10 is greater than 5', () => {
expect(10).toBeGreaterThan(5);
});
You can also use test()
instead of it()
, as both are aliases.
3. expect
β Assertions for Checking Values
expect
is used to make assertions about test outcomes.
β Basic Assertions:
expect(2 + 2).toBe(4); // Checks exact match
expect([1, 2]).toEqual([1, 2]); // Checks deep equality
β Checking for Truthiness:
expect(null).toBeNull();
expect(undefined).toBeUndefined();
expect(true).toBeTruthy();
expect(false).toBeFalsy();
β Number Comparisons:
expect(10).toBeGreaterThan(5);
expect(3.14).toBeCloseTo(3.1, 1); // Compares with precision
β String Matching:
expect('hello world').toMatch(/world/);
β Array & Object Matching:
expect([1, 2, 3]).toContain(2);
expect({ name: 'Akash' }).toHaveProperty('name', 'Akash');
Testing React component
Vitest works seamlessly with the React testing library to test React components.
Install Dependencies
npm install -D vitest @testing-library/react @testing-library/jest-dom jsdom
Key Testing Concepts
β
render()
β Renders the component in a virtual DOM.
β
screen.getByText()
β Finds an element with matching text.
β
fireEvent.click
()
β Simulates a click event.
β
expect(...).toBeInTheDocument()
β Checks if an element is present.
β
vi.fn()
β Mocks a function to track calls.
β
expect(fn).toHaveBeenCalledTimes(n)
β Verifies function execution.
Letβs test a simple component
const Greet = ({ name }: { name?: string }) => {
if (name) return <h1>Hello {name}</h1>;
return <button>Login</button>;
};
export default Greet;
Test for this component will be like
import { render, screen } from "@testing-library/react";
import { it, expect, describe } from "vitest";
import Greet from "../components/Greet";
import "@testing-library/jest-dom/vitest";
describe("Greet", () => {
it("should Display name if name is passed unless show login", () => {
render(<Greet name="Akash" />);
const heading = screen.getByRole("heading");
expect(heading).toBeInTheDocument();
expect(heading).toHaveTextContent(/akash/i);
});
it("should render login button when name is not passed", () => {
render(<Greet />);
const loginBtn = screen.getByRole("button");
expect(loginBtn).toBeInTheDocument();
expect(loginBtn).toHaveTextContent(/login/i);
});
});
Testing a component having conditionally rendered content
type User = {
id: number;
name: string;
isAdmin?: boolean;
};
const UserAccount = ({ user }: { user: User }) => {
return (
<>
<h2>User Profile</h2>
{user.isAdmin && <button>Edit</button>}
<div>
<strong>Name:</strong> {user.name}
</div>
</>
);
};
export default UserAccount;
Test
import { render, screen } from "@testing-library/react";
import UserAccount from "../components/UserAccount";
describe("UserAccont", () => {
it("should display edit button if admin", () => {
render(
<UserAccount
user={{
id: 1,
name: "Akash",
isAdmin: true,
}}
/>
);
const editBtn = screen.getByRole("button");
expect(editBtn).toBeInTheDocument();
expect(editBtn).toHaveTextContent("Edit");
});
it("should not display edit button if not admin", () => {
render(
<UserAccount
user={{
id: 1,
name: "Akash",
isAdmin: false,
}}
/>
);
const editBtn = screen.queryByRole("button");
expect(editBtn).not.toBeInTheDocument();
});
it("should diplay user name", () => {
render(
<UserAccount
user={{
id: 1,
name: "Akash",
isAdmin: false,
}}
/>
);
expect(screen.getByText("Akash")).toBeInTheDocument();
});
});
Testing List
type User = {
id: number;
name: string;
isAdmin?: boolean;
};
const UserList = ({ users }: { users: User[] }) => {
if (users.length === 0) return <p>No users available.</p>;
return (
<ul>
{users.map((user) => (
<li key={user.id}>
<a href={`/users/${user.id}`}>{user.name}</a>
</li>
))}
</ul>
);
};
export default UserList;
import UserList from "../components/UserList";
import { User } from "../entities";
import { render, screen } from "@testing-library/react";
describe("User List", () => {
it("should print no user available if user[] is empty", () => {
const users: User[] = [];
render(<UserList users={users} />);
const msg = screen.getByRole("paragraph");
expect(msg).toBeInTheDocument();
expect(msg).toHaveTextContent(/no users available/i);
});
it("should print no user available if user[] is empty", () => {
const users: User[] = [];
render(<UserList users={users} />);
const msg = screen.getByRole("paragraph");
expect(msg).toBeInTheDocument();
expect(msg).toHaveTextContent(/no users available/i);
});
it("should print list of users", () => {
const users: User[] = [
{ id: 1, name: "Akash" },
{ id: 2, name: "John" },
];
render(<UserList users={users} />);
users.forEach((list) => {
const link = screen.getByRole("link", { name: list.name });
expect(link).toBeInTheDocument();
expect(link).toHaveAttribute("href", `/users/${list.id}`);
});
});
});
Testing a component having image
const ProductImageGallery = ({ imageUrls }: { imageUrls: string[] }) => {
if (imageUrls.length === 0) return null;
return (
<ul>
{imageUrls.map((url) => (
<li key={url}>
<img src={url} />
</li>
))}
</ul>
);
};
export default ProductImageGallery;
import { render, screen } from "@testing-library/react";
import ProductImageGallery from "../components/ProductImageGallery";
describe("ProductImage", () => {
it("should dispaly nothing if imageUrl[] is empty", () => {
const { container } = render(<ProductImageGallery imageUrls={[]} />);
expect(container).toBeEmptyDOMElement();
});
it("should list of imageUrls", () => {
const imageUrls: string[] = [
"https://www.google.com",
"https://www.github.com",
];
render(<ProductImageGallery imageUrls={imageUrls} />);
const images = screen.getAllByRole("img");
images.forEach((img, idx) => {
expect(images[idx]).toHaveAttribute("src", imageUrls[idx]);
});
});
});
Testing User Interaction
import { useState } from "react";
const TermsAndConditions = () => {
const [isChecked, setIsChecked] = useState(false);
return (
<div>
<h1>Terms & Conditions</h1>
<p>
Lorem ipsum dolor, sit amet consectetur adipisicing elit. Autem,
delectus.
</p>
<div className="pb-3">
<label htmlFor="agree">
<input
type="checkbox"
id="agree"
checked={isChecked}
onChange={() => setIsChecked(!isChecked)}
className="mr-1"
/>
I accept the terms and conditions.
</label>
</div>
<button disabled={!isChecked} className="btn">
Submit
</button>
</div>
);
};
export default TermsAndConditions;
import { render, screen } from "@testing-library/react";
import TermsAndConditions from "../components/TermsAndConditions";
import userEvent from "@testing-library/user-event";
describe("Terms&Conditions", () => {
it("should display heading and render checkbox", () => {
render(<TermsAndConditions />);
const heading = screen.getByRole("heading");
expect(heading).toBeInTheDocument();
expect(heading).toHaveTextContent(/terms & conditions/i);
const checkbox = screen.getByRole("checkbox");
expect(checkbox).toBeInTheDocument();
expect(checkbox).not.toBeChecked();
const button = screen.getByRole("button");
expect(button).toBeInTheDocument();
expect(button).toHaveTextContent(/submit/i);
expect(button).toBeDisabled();
});
it("should be enabled when checkbox is checked", async () => {
render(<TermsAndConditions />);
const checkbox = screen.getByRole("checkbox");
const user = userEvent.setup();
await user.click(checkbox);
const button = screen.getByRole("button");
expect(button).toBeInTheDocument();
expect(button).toBeEnabled();
});
});
Some more User Interaction Testing
import { useState } from "react";
const ExpandableText = ({ text }: { text: string }) => {
const limit = 255;
const [isExpanded, setExpanded] = useState(false);
if (text.length <= limit) return <article>{text}</article>;
return (
<div>
{isExpanded ? (
<article>{text}</article>
) : (
<article>{text.substring(0, limit)}...</article>
)}
<button className="btn" onClick={() => setExpanded(!isExpanded)}>
{isExpanded ? "Show Less" : "Show More"}
</button>
</div>
);
};
export default ExpandableText;
import { render, screen } from "@testing-library/react";
import ExpandableText from "../components/ExpandableText";
import userEvent from "@testing-library/user-event";
describe("Expandable Text", () => {
it("should display only article if text under limit", () => {
render(<ExpandableText text="hello world!" />);
expect(screen.getByRole("article")).toBeInTheDocument();
expect(screen.queryByRole("button")).not.toBeInTheDocument();
});
it("should display only 255 characters if show more is not clicked", async () => {
const text =
"Lorem ipsum dolor, sit amet consectetur adipisicing elit. Vel autem molestiae doloribus voluptates natus repellendus tempora consectetur laboriosam? Quam distinctio odio nobis consequuntur repellendus possimus ab saepe minus. Nam eum non pariatur, in nulla nisi harum asperiores delectus quod animi, voluptates officia consequuntur, eveniet est porro quidem ullam? Ex suscipit deleniti delectus quas voluptatem quibusdam minus modi dicta, natus, inventore adipisci ipsam dolores voluptates.";
render(<ExpandableText text={text} />);
const trucatedtext = text.substring(0, 255) + "...";
const showMore = screen.getByRole("button");
expect(showMore).toBeInTheDocument();
expect(screen.getByRole("article")).toBeInTheDocument();
expect(screen.getByText(trucatedtext)).toBeInTheDocument();
expect(showMore).toHaveTextContent(/show more/i);
const user = userEvent.setup();
await user.click(showMore);
expect(showMore).toHaveTextContent(/show less/i);
expect(screen.getByRole("article")).toBeInTheDocument();
expect(screen.getByRole("article")).toHaveTextContent(text);
await user.click(showMore);
expect(screen.getByRole("article")).toBeInTheDocument();
expect(screen.getByText(trucatedtext)).toBeInTheDocument();
expect(showMore).toHaveTextContent(/show more/i);
});
});
Final Thoughts
Vitest provides a fast and efficient way to test React applications. It offers a Jest-compatible API while leveraging the power of Vite. By integrating Vitest with the React Testing Library, developers can ensure their applications remain robust, scalable, and free from unexpected bugs.
Subscribe to my newsletter
Read articles from Akash Prasad directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Akash Prasad
Akash Prasad
I am a Full Stack Developer and an AI enthusiast!