Setting Up Unit Testing in React Vite with Vitest: A Practical Guide
In this tutorial, we’ll walk through setting up and implementing unit tests for a simple React Todo application using Vitest and React Testing Library. We’ll create tests for both the main App component and a TodoList component while explaining each step in detail.
Project Setup
First, let’s create a new React project using Vite:
npm create vite@latest learn-vitest
The Application Code
Our application consists of two main components: App.tsx
and a TodoList
component. The App component serves as our main container, while TodoList handles the todo management functionality.
The TodoList component implements basic features like:
- Adding new todos
- Toggling todo completion status
- Maintaining a list of todos in state
Setting Up Vitest
To get started with testing, we need to install Vitest and configure it properly:
npm i -D vitest happy-dom @testing-library/react @testing-library/jest-dom
Understanding the Vite Configuration
In our vite.config.ts
, we've added specific test configuration:
test: {
environment: 'happy-dom',
setupFiles: ['./src/tests/setup.ts'],
include: ['src/**/*.{test,spec}.{ts,tsx}']
}
This configuration does several important things:
environment: 'happy-dom'
: Sets up a DOM-like environment for testingsetupFiles
: Points to our test setup fileinclude
: Specifies which files should be treated as test files (those ending in .test.ts/tsx or .spec.ts/tsx)
Test Setup File
The setup.ts
file is crucial for our testing environment:
import { afterEach } from 'vitest';
import { cleanup } from '@testing-library/react';
import "@testing-library/jest-dom/vitest"
afterEach(() => {
cleanup();
});
This setup file:
- Imports necessary testing utilities
- Ensures cleanup after each test (preventing test pollution)
- Adds jest-dom matchers for better assertions
- Runs cleanup automatically after each test to maintain a clean testing environment
Writing Our Tests
Testing the App Component
Our App component test (App.test.tsx
) verifies the basic structure:
describe("App Component", () => {
it("should render the app title in h1", () => {
render(<App />);
const heading = screen.getByTestId("title");
expect(heading).toBeInTheDocument();
expect(heading).toHaveTextContent(/React Testing Demo$/);
expect(heading.tagName).toBe("H1");
});
});
This test:
- Renders the App component
- Checks if the title exists using a data-testid
- Verifies the content and HTML tag of the title
Testing the TodoList Component
The TodoList tests (todo-list.test.tsx
) are more comprehensive:
describe("Todo list Component", () => {
beforeEach(() => {
render(<TodoList />);
});
it("should render the todo list with initial empty state", () => {
const todoList = screen.getByTestId("todo-list");
expect(todoList).toBeInTheDocument();
expect(todoList.children).toHaveLength(0);
});
it("should add a new todo when input is filled and add button is clicked", async () => {
const input = screen.getByTestId("todo-input");
const addButton = screen.getByTestId("add-todo-button");
fireEvent.change(input, { target: { value: "New Todo Item" } });
fireEvent.click(addButton);
await waitFor(() => {
const todoList = screen.getByTestId("todo-list");
expect(todoList.children).toHaveLength(1);
expect(screen.getByText("New Todo Item")).toBeInTheDocument();
expect(input).toHaveValue("");
});
});
});
These tests verify:
- Initial empty state of the todo list
- Adding new todos through user interaction
- Proper state updates after adding todos
Key testing concepts demonstrated:
- Using
beforeEach
for test setup - Testing user interactions with
fireEvent
- Handling asynchronous updates with
waitFor
- Using data-testid attributes for reliable element selection
Running the Tests
To run your tests, add a test script to your package.json:
{
"scripts": {
"test": "vitest"
}
}
Then run:
npm test
Video Tutorial
If you prefer a video walkthrough of this setup, check out the detailed tutorial here: https://youtu.be/SMsZDg-t_mA