Testing de componentes con React Testing Library

Intermedio
React
React
Actualizado: 22/04/2026

Diagrama: testing de componentes con React Testing Library

React Testing Library (RTL) es la librería oficial recomendada por el equipo de React para testear componentes. Su filosofía es probar los componentes como los usa una persona real: buscando textos visibles, roles accesibles y etiquetas de formulario, en lugar de inspeccionar la estructura interna del componente. Un test escrito con RTL sigue funcionando aunque refactorices la implementación, siempre que el comportamiento público del componente no cambie.

Render y acceso a screen

La función render monta el componente en un DOM virtual (el jsdom configurado en Vitest) y devuelve utilidades para interactuar con él. La forma idiomática de escribir tests usa render junto con el objeto global screen, que expone las queries sobre el DOM renderizado.

// Saludo.tsx
interface SaludoProps {
  nombre: string;
}

export function Saludo({ nombre }: SaludoProps) {
  return <h1>Hola, {nombre}</h1>;
}
// Saludo.test.tsx
import { describe, it, expect } from "vitest";
import { render, screen } from "@testing-library/react";
import { Saludo } from "./Saludo";

describe("Saludo", () => {
  it("muestra el nombre en un encabezado", () => {
    render(<Saludo nombre="María González" />);
    expect(screen.getByText("Hola, María González")).toBeInTheDocument();
  });
});

El matcher toBeInTheDocument proviene del paquete @testing-library/jest-dom que instalaste en la lección anterior. Confirma que el elemento existe realmente en el DOM en el momento de la aserción.

Queries: getBy, queryBy y findBy

Las queries de RTL vienen en tres familias que cubren escenarios distintos:

  • getBy...: lanza una excepción si el elemento no existe. Se usa cuando estás seguro de que el elemento debe estar.
  • queryBy...: devuelve null si el elemento no existe. Se usa para verificar la ausencia de un elemento.
  • findBy...: devuelve una promesa que se resuelve cuando el elemento aparece (hasta el timeout). Se usa para aserciones asíncronas.
import { render, screen } from "@testing-library/react";

render(<MiComponente />);

// Seguro que el título está
expect(screen.getByRole("heading", { level: 1 })).toBeInTheDocument();

// El mensaje de error no debe estar al inicio
expect(screen.queryByText(/error/i)).not.toBeInTheDocument();

// Un valor llega de forma asíncrona
expect(await screen.findByText("Datos cargados")).toBeInTheDocument();

La prioridad de selectores recomendada por la librería es, en este orden: getByRole, getByLabelText, getByPlaceholderText, getByText, getByDisplayValue y, como último recurso, getByTestId. Seleccionar por rol es lo más cercano a cómo un usuario con lector de pantalla percibe la página, y refuerza que los componentes sean accesibles desde el primer momento.

screen.getByRole("button", { name: /enviar/i });
screen.getByRole("textbox", { name: "Correo electrónico" });
screen.getByLabelText("Contraseña");
screen.getByPlaceholderText("Escribe tu búsqueda");

Evita getByTestId salvo cuando no exista otra forma razonable de localizar el elemento. Un test que depende de data-testid se rompe silenciosamente si alguien elimina el atributo, mientras que un test basado en el rol reflejará que la accesibilidad del componente se ha degradado.

Simular interacciones con fireEvent y userEvent

Para probar la interactividad hay dos APIs. fireEvent dispara eventos sintéticos en un solo paso y es la más directa. userEvent simula la secuencia completa de eventos que genera una acción real del usuario (focus, keydown, input, keyup) y es el estándar recomendado a partir de la versión 14.

// Contador.tsx
import { useState } from "react";

export function Contador() {
  const [valor, setValor] = useState(0);
  return (
    <div>
      <p>Total: {valor}</p>
      <button onClick={() => setValor((v) => v + 1)}>Incrementar</button>
    </div>
  );
}
// Contador.test.tsx
import { describe, it, expect } from "vitest";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { Contador } from "./Contador";

describe("Contador", () => {
  it("incrementa el valor al pulsar el botón", async () => {
    const user = userEvent.setup();
    render(<Contador />);

    expect(screen.getByText("Total: 0")).toBeInTheDocument();

    await user.click(screen.getByRole("button", { name: /incrementar/i }));
    await user.click(screen.getByRole("button", { name: /incrementar/i }));

    expect(screen.getByText("Total: 2")).toBeInTheDocument();
  });
});

El método userEvent.setup() crea una instancia aislada por test, lo que evita interacciones cruzadas entre tests. Todas las acciones del usuario son asíncronas porque simulan el paso real del tiempo (por ejemplo, al escribir con user.type).

Interacción con formularios

Los formularios controlan su estado con las props value y onChange. En los tests se reproduce la experiencia del usuario escribiendo, enviando y verificando el resultado.

// Formulario.test.tsx
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { Formulario } from "./Formulario";

it("envía el formulario con los datos introducidos", async () => {
  const user = userEvent.setup();
  render(<Formulario />);

  await user.type(screen.getByLabelText("Nombre"), "María González");
  await user.type(screen.getByLabelText("Email"), "maria.gonzalez@empresa.es");
  await user.click(screen.getByRole("button", { name: /enviar/i }));

  expect(await screen.findByText(/gracias, maría/i)).toBeInTheDocument();
});

Aserciones sobre el DOM

Una vez localizado el elemento, los matchers de jest-dom expresan el estado esperado de la interfaz de forma declarativa.

expect(screen.getByRole("button")).toBeDisabled();
expect(screen.getByRole("button")).toHaveTextContent("Cargando");
expect(screen.getByLabelText("Email")).toHaveValue("maria.gonzalez@empresa.es");
expect(screen.getByRole("alert")).toHaveClass("error");
expect(screen.getByRole("dialog")).toBeVisible();

Cada matcher comprueba una propiedad específica del DOM o del accesibility tree. Usarlos en lugar de aserciones genéricas sobre innerHTML hace que los tests sean más fiables y que las regresiones de accesibilidad aparezcan como fallos de test y no como tickets posteriores de auditoría.

Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, React es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de React

Explora más contenido relacionado con React y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

Renderizar componentes con render de React Testing Library. Localizar elementos con screen y las queries getByText, getByRole, getByLabelText. Simular interacciones con fireEvent y userEvent. Diferenciar getBy, queryBy y findBy según el escenario.