
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
nullsi 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
getByTestIdsalvo cuando no exista otra forma razonable de localizar el elemento. Un test que depende dedata-testidse 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
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.