Skip to content

Part 1.6 — Testing React Components with Jest (Preview of Part 5)

Site Console Site Console
4 min read Updated Nov 29, 2025 Software Tools 0 comments
Testing React Components with Jest and React Testing Library

Testing isn’t about catching bugs you know exist — it’s about preventing bugs you never anticipated.
As your React app grows, tests act as guardrails that keep your UI stable, even as you refactor or expand features.

This post introduces Jest and React Testing Library (RTL) — the modern standard for testing React components. You’ll write clean, type-safe tests that verify your UI behaves the way users expect.


1. Why Test React Components?

A few reasons why full-stack engineers invest heavily in frontend tests:

  • Prevent regressions as features grow

  • Validate key user flows before integrating with the backend

  • Catch type, logic, and state bugs early

  • Enable safe refactoring

  • Increase team confidence

Testing feels slow at first — but it saves hours when your app hits real users.


2. Install Testing Dependencies

Inside your React workspace (apps/web):

pnpm add -D jest @types/jest ts-jest @testing-library/react @testing-library/jest-dom

Then initialize Jest:

pnpm ts-jest config:init

This generates a jest.config.js.

Update it to support JSX and modern React testing:

module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'jsdom',
  setupFilesAfterEnv: ['@testing-library/jest-dom'],
};

Now you're ready to test components in a DOM-like environment.


3. Create a Simple Component to Test

Let’s test a small, deterministic component so the concepts are clear.

// src/components/Counter.tsx
import { useState } from 'react';

export function Counter() {
  const [count, setCount] = useState(0);

  return (
    <section>
      <p>Count: {count}</p>
      <button onClick={() => setCount(prev => prev + 1)}>Increment</button>
    </section>
  );
}

A perfect candidate for a basic unit test.


4. Writing Your First Test

Create a test file:

src/components/Counter.test.tsx

Add:

import { render, screen, fireEvent } from '@testing-library/react';
import { Counter } from './Counter';

test('increments the counter when clicking the button', () => {
  render(<Counter />);

  expect(screen.getByText(/Count: 0/)).toBeInTheDocument();

  fireEvent.click(screen.getByRole('button', { name: /increment/i }));

  expect(screen.getByText(/Count: 1/)).toBeInTheDocument();
});

What’s happening here?

  • render mounts the component into a virtual DOM

  • screen.getByText mirrors how real users search for information

  • fireEvent.click simulates the button interaction

  • The final assertion verifies the count incremented

This pattern recreates realistic user behavior — the core philosophy of RTL.


5. Testing Props and Variants

Let’s test a reusable component from earlier — a Button variant.

// src/components/Button.tsx
interface ButtonProps {
  label: string;
  variant?: 'primary' | 'secondary';
  onClick: () => void;
}

export function Button({ label, variant = 'primary', onClick }: ButtonProps) {
  return (
    <button className={`btn-${variant}`} onClick={onClick}>
      {label}
    </button>
  );
}

Test file:

import { render, screen } from '@testing-library/react';
import { Button } from './Button';

test('renders the button label', () => {
  render(<Button label="Save" onClick={() => {}} />);
  expect(screen.getByText('Save')).toBeInTheDocument();
});

test('applies the correct variant class', () => {
  render(<Button label="Cancel" variant="secondary" onClick={() => {}} />);
  const button = screen.getByRole('button', { name: /cancel/i });
  expect(button).toHaveClass('btn-secondary');
});

Small, isolated tests like these are easy to maintain and reason about.


6. Testing Form Components

Forms introduce events, controlled components, and validation logic.

Example: Test the AddTodoForm from the previous post.

// src/components/AddTodoForm.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { AddTodoForm } from './AddTodoForm';

test('submits a todo and clears the input', () => {
  const mockAdd = vi.fn(); // if using Vitest; for Jest use jest.fn()
  render(<AddTodoForm onAdd={mockAdd} />);

  const input = screen.getByPlaceholderText(/add a new todo/i);
  const button = screen.getByRole('button', { name: /add/i });

  fireEvent.change(input, { target: { value: 'Buy milk' } });
  fireEvent.click(button);

  expect(mockAdd).toHaveBeenCalledWith('Buy milk');
  expect(input).toHaveValue('');
});

This test confirms:

  • Controlled input works

  • Submit logic fires

  • Input resets cleanly

You’re testing behavior — not implementation details.


7. Snapshot Testing (Use Sparingly)

You can create snapshots to catch unexpected UI changes:

import { render } from '@testing-library/react';
import { TodoItem } from './TodoItem';

test('renders todo item snapshot', () => {
  const todo = { id: 1, text: 'Learn NestJS', completed: false };
  const { container } = render(
    <TodoItem todo={todo} onToggle={() => {}} onDelete={() => {}} />
  );
  expect(container).toMatchSnapshot();
});

Snapshots can become noisy — use them for stable components only.


8. Running Your Tests

Add a script to package.json:

"scripts": {
  "test": "jest"
}

Run:

pnpm test

If everything is green, you’ve built your first test suite.


9. Why This Matters for the Full Stack

Later, you’ll test:

  • API calls from React to your NestJS backend

  • Error handling and loading states

  • Protected routes after authentication

  • End-to-end flows using Playwright

This small foundation prepares you for the much deeper testing work in Part 5.

Tests don’t slow you down — they let you ship fearlessly.


10. Wrap-Up

In this post, you learned how to:

  • Set up Jest + React Testing Library

  • Test components, props, events, and forms

  • Simulate real user interactions

  • Assert UI behavior safely

  • Build a testing mindset early in development

You now have all core skills from Part 1: components, props, state, events, forms, and testing.

Next, we move to Part 2 — Talking to Servers: Fetch, Forms, CRUD, UX State, where your React skills start interacting with real data over REST APIs.

Related

Leave a comment

Sign in to leave a comment.

Comments