Testing React Applications: Vitest & React Testing Library Guide

Testing React Applications: Vitest & React Testing Library Guide

Why Vitest?

Vitest is a next-generation test runner powered by Vite. It provides near-instant HMR re-runs, out-of-the-box TypeScript support, and Jest API compatibility.


1. Writing Logic Unit Tests

import { describe, it, expect } from 'vitest';

describe('math helper', () => {
  it('adds numbers correctly', () => {
    expect(1 + 1).toBe(2);
  });
});

Conclusion

Automated test suites ensure code stability and enable confident continuous deployment.

A complete exercise: installation to the first test

This exercise tests a synchronous React component in a simulated DOM. Use a Node version supported by your installed packages. Merge these settings into an existing Vitest configuration so project aliases and plugins remain available.

1. Install dependencies and configure the environment

npm install -D vitest jsdom @vitejs/plugin-react @testing-library/react @testing-library/dom @testing-library/jest-dom @testing-library/user-event
// vitest.config.ts
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  test: { environment: 'jsdom', setupFiles: ['./test/setup.ts'] },
});
// test/setup.ts
import '@testing-library/jest-dom/vitest';
import { afterEach } from 'vitest';
import { cleanup } from '@testing-library/react';

afterEach(cleanup);

2. Test the behavior users see

Save this example as Counter.test.tsx. Locate the button by its role and accessible name, await the interaction, and then inspect the output. This checks behavior without depending on internal variable names.

// Counter.test.tsx
import { useState } from 'react';
import { describe, expect, it } from 'vitest';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>Count: {count}</button>;
}

describe('Counter', () => {
  it('updates the count after a click', async () => {
    const user = userEvent.setup();
    render(<Counter />);
    await user.click(screen.getByRole('button', { name: 'Count: 0' }));
    expect(screen.getByRole('button', { name: 'Count: 1' })).toBeInTheDocument();
  });
});
npx vitest run

Expect one passing test. Remove the setCount call temporarily to verify the test fails when the button stops working, then restore the code.

3. Diagnose common failures

  • document is not defined: check that the test uses jsdom rather than only the Node environment.
  • toBeInTheDocument is unavailable: check the jest-dom/vitest import and setupFiles path.
  • An element cannot be found: inspect its accessible name and await asynchronous work before asserting.

This test does not prove browser layout, database integration, or every application flow is correct. Add integration or browser tests for those behaviors. Coverage highlights unexecuted paths; it does not guarantee meaningful assertions.

To choose testable boundaries, continue with frontend architecture.

References: Vitest, React Testing Library, user-event.