How to Test Uncontrolled Components in React

Testing uncontrolled components in React requires a different approach than testing controlled ones because uncontrolled components rely on the DOM itself to store and manage their state. This article provides a straightforward guide on how to test uncontrolled React components using React Testing Library and Jest, focusing on simulating real user interactions and asserting that the DOM state is updated correctly.

Understanding Uncontrolled Components

In an uncontrolled component, form data is handled by the DOM rather than by React state. Instead of writing an event handler for every state update, you use a ref to pull values from the DOM when you need them.

Because the component state is managed internally by the DOM, unit tests must interact directly with DOM nodes to set values and trigger events, mimicking how a real user interacts with the application.

Step-by-Step Testing Guide

To test an uncontrolled component, you need to render the component, query the DOM elements, simulate user input, and assert that the correct values are captured upon an action (like a form submission).

Here is a simple uncontrolled form component:

import React, { useRef } from 'react';

function UncontrolledForm({ onSubmit }) {
  const inputRef = useRef(null);

  const handleSubmit = (event) => {
    event.preventDefault();
    onSubmit(inputRef.current.value);
  };

  return (
    <form onSubmit={handleSubmit}>
      <label htmlFor="username">Username</label>
      <input id="username" type="text" ref={inputRef} />
      <button type="submit">Submit</button>
    </form>
  );
}

export default UncontrolledForm;

Writing the Test

Using React Testing Library and @testing-library/user-event, you can write a test that simulates a user typing into the input field and submitting the form.

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import UncontrolledForm from './UncontrolledForm';

test('submits the correct value from uncontrolled input', async () => {
  const mockSubmit = jest.fn();
  render(<UncontrolledForm onSubmit={mockSubmit} />);

  // Locate the input and button elements
  const inputElement = screen.getByLabelText(/username/i);
  const submitButton = screen.getByRole('button', { name: /submit/i });

  // Simulate user typing into the uncontrolled input
  await userEvent.type(inputElement, 'JSDeveloper');

  // Simulate form submission
  await userEvent.click(submitButton);

  // Assert that the submission handler was called with the correct value
  expect(mockSubmit).toHaveBeenCalledTimes(1);
  expect(mockSubmit).toHaveBeenCalledWith('JSDeveloper');
});

Key Takeaways for Testing Uncontrolled Components