Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Testing

A test is code that checks by itself that the app works correctly: shows the right text, reacts to clicks. Tests run with one command and find bugs before users see them. Vite projects use Vitest — a test runner — and Testing Library — a set of functions to render a component and find elements in it the way a person would.

Installing

In the to-do list project folder run:

npm install -D vitest jsdom @testing-library/react @testing-library/dom @testing-library/user-event @testing-library/jest-dom

jsdom imitates a browser inside Node.js, so components can render without a real window. The -D flag records the packages as development dependencies.

Configuring

Add a test section to vite.config.js:

// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
 
export default defineConfig({
  plugins: [react()],
  test: {
    environment: 'jsdom',
    globals: true,
    setupFiles: './src/setupTests.js',
  },
});

Create src/setupTests.js — it adds handy checks such as toBeInTheDocument:

// src/setupTests.js
import '@testing-library/jest-dom/vitest';

And add a command to the scripts section of package.json: "test": "vitest".

A first test

A test lives next to its component and has the same name with .test.jsx. Create src/components/TodoList.test.jsx:

// src/components/TodoList.test.jsx
import { render, screen } from '@testing-library/react';
import { expect, test } from 'vitest';
import TodoList from './TodoList.jsx';
 
test('shows a message when there are no tasks', () => {
  render(<TodoList todos={[]} />);
 
  expect(screen.getByText('No tasks. Time to rest.')).toBeInTheDocument();
});

render renders the component, screen.getByText finds an element with that text. Run npm test — the terminal shows a green mark and 1 passed. Vitest keeps watching the files and reruns the tests on save.

User actions

The user-event library clicks buttons and types text like a real user. Let us test the form — src/App.test.jsx:

// src/App.test.jsx
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { expect, test } from 'vitest';
import App from './App.jsx';
 
test('adds a task to the list', async () => {
  const user = userEvent.setup();
  render(<App />);
 
  await user.type(screen.getByRole('textbox'), 'Water the plants');
  await user.click(screen.getByRole('button', { name: 'Add' }));
 
  expect(screen.getByText('Water the plants')).toBeInTheDocument();
});

getByRole finds an element by its role: textbox is an input field, button is a button with the given text. The test types text, clicks "Add" and checks that the task appeared.

Ways to find elements

FunctionWhen to use it
getByRoleButtons, fields, links — the preferred way
getByTextPlain text on the page
getByLabelTextA field that has a label
queryByTextCheck that an element is absent: returns null
findByTextWait for an element that appears later

Common mistake

user-event actions are asynchronous. Without await the check runs before typing finishes, and the test fails:

// Wrong:
user.type(screen.getByRole('textbox'), 'Water the plants');
 
// Correct:
await user.type(screen.getByRole('textbox'), 'Water the plants');

Mark the test function as async and put await before every user action.

Thanks for reading!

Donate to the site — we work hard for you.