We use cookies to enhance your experience on the site
CodeWorlds

React Testing Library -- Mission Control Panel

React Testing Library (RTL) is a library that changes how we test React components. Instead of testing internal implementation, RTL focuses on what the user sees and does -- exactly like a mission control panel that shows the pilot only what matters.

The Core Principle of RTL

"The more your tests resemble the way your software is used, the more confidence they can give you." -- Kent C. Dodds

Rendering Components

The

render
function is the foundation of RTL -- it fires up a component in a virtual DOM:

1import { render, screen } from '@testing-library/react';
2import SpaceshipDashboard from './SpaceshipDashboard';
3
4test('renders spaceship dashboard', () => {
5  render(<SpaceshipDashboard pilotName="Nova" />);
6  // Component is now available for testing
7  expect(screen.getByText('Welcome, Nova!')).toBeInTheDocument();
8});

Queries -- Finding Elements

RTL offers three query families, each for a different purpose:

getBy -- Finds and Requires

1// Throws error if element does NOT exist
2const heading = screen.getByText('Mission Control');
3const input = screen.getByRole('textbox');
4const img = screen.getByAltText('spaceship');

queryBy -- Finds but Doesn't Require

1// Returns null if element does NOT exist (no error)
2const error = screen.queryByText('Error occurred');
3expect(error).not.toBeInTheDocument();

findBy -- Finds Asynchronously

1// Waits for element to appear (returns Promise)
2const data = await screen.findByText('Data loaded');
3expect(data).toBeInTheDocument();

Types of Selectors

Each query family has variants:

ByRole -- Best Practice!

1// Searches by ARIA role -- the best approach!
2screen.getByRole('button', { name: 'Launch' });
3screen.getByRole('heading', { level: 1 });
4screen.getByRole('textbox', { name: /pilot name/i });
5screen.getByRole('checkbox', { checked: true });
6screen.getByRole('link', { name: 'Dashboard' });

ByText -- Searches by Text

1screen.getByText('Mission Status: Active');
2screen.getByText(/mission/i); // regex, case-insensitive

ByLabelText -- Searches by Form Label

1// <label htmlFor="speed">Speed</label>
2// <input id="speed" />
3screen.getByLabelText('Speed');

ByPlaceholderText -- Searches by Placeholder

1screen.getByPlaceholderText('Enter coordinates');

ByTestId -- Last Resort

1// <div data-testid="fuel-gauge">...</div>
2screen.getByTestId('fuel-gauge');

Selector Priority

RTL recommends the following priority (from best):

  1. getByRole -- accessibility, how user sees the element
  2. getByLabelText -- forms
  3. getByPlaceholderText -- when there's no label
  4. getByText -- non-interactive elements
  5. getByDisplayValue -- current input value
  6. getByAltText -- images
  7. getByTitle -- title attribute
  8. getByTestId -- last resort, when nothing else fits

jest-dom Matchers

The

@testing-library/jest-dom
library adds special matchers:

1// Visibility
2expect(element).toBeVisible();
3expect(element).toBeInTheDocument();
4
5// Attributes
6expect(button).toBeDisabled();
7expect(input).toBeRequired();
8expect(input).toHaveValue('Apollo');
9
10// CSS classes
11expect(element).toHaveClass('active');
12
13// Text
14expect(element).toHaveTextContent('Mission');
15
16// Styles
17expect(element).toHaveStyle({ color: 'green' });

Rendering with Context

Components often need providers (Router, Theme, Store):

1function renderWithProviders(ui, options = {}) {
2  function Wrapper({ children }) {
3    return (
4      <ThemeProvider theme="dark">
5        <MissionProvider>
6          {children}
7        </MissionProvider>
8      </ThemeProvider>
9    );
10  }
11  return render(ui, { wrapper: Wrapper, ...options });
12}
13
14// Usage
15test('renders themed dashboard', () => {
16  renderWithProviders(<Dashboard />);
17  expect(screen.getByText('Dark Mode')).toBeInTheDocument();
18});

Debug -- When Tests Fail

When a test fails and you don't know why, use

screen.debug()
to inspect the current DOM state:

1test('shows mission status', () => {
2  render(<MissionPanel />);
3
4  // Displays current DOM state in the console
5  screen.debug();
6
7  // You can also debug a specific element
8  const panel = screen.getByRole('main');
9  screen.debug(panel);
10});

screen.debug()
will display the HTML of the entire rendered component, which helps understand why a selector can't find an element. It's like a diagnostic system on the spaceship -- when something doesn't work, you check the logs first!

React Testing Library is the foundation of modern React component testing. Instead of testing implementation details, you test what really matters -- the user experience.

Go to CodeWorlds