Lección 2 de 411 min
Pruebas de componentes
Testing Library, Angular Testing Utilities, Vue Test Utils y por qué Enzyme quedó atrás.
Un rediseño cambia las clases CSS del formulario de inicio de sesión. Para quien lo usa no ha cambiado nada, pero decenas de pruebas fallan porque buscaban el botón por su clase. La misma semana, alguien sustituye el botón por un <div> con estilos y ninguna prueba se queja, aunque ya no se puede usar con el teclado.
Las pruebas de componentes comprueban piezas de interfaz: las pintan, interactúan con ellas y revisan lo que aparece en pantalla. En esta lección verás cómo escribirlas para que fallen cuando algo se rompe de verdad, y no cuando cambias un detalle interno, con Testing Library, las utilidades de Angular y Vue Test Utils.
Probar lo que ve el usuario
Qué es una prueba de componente
Pintar un componente, interactuar con él y comprobar lo que aparece en pantalla.
Una prueba de componente monta un componente de forma aislada (un formulario, una tarjeta, un menú), simula lo que haría una persona (escribir, pulsar, elegir) y comprueba el resultado visible: un mensaje de error, un botón desactivado, una lista que se actualiza. Está a medio camino entre la prueba unitaria y la end-to-end.
Normalmente se ejecuta en Node con un DOM simulado, como jsdom, lo que la hace rápida. Ese DOM no calcula estilos ni posiciones, así que no sirve para comprobar si algo se ve bien; para eso existen modos que ejecutan las pruebas en un navegador real.
La regla de oro es probar el comportamiento, no la implementación: lo que el usuario ve y hace, no el estado interno ni los nombres de las funciones. Así puedes reescribir el componente por dentro sin tocar sus pruebas.
Úsalo cuando
Para formularios, validaciones, estados de carga y de error, y cualquier interacción de un componente que no necesite el backend real.
Ten en cuenta
Si la prueba necesita conocer el estado interno o el nombre de un método privado, está demasiado acoplada. Y las dependencias externas, como la API, sustitúyelas con dobles de prueba, como viste en la lección anterior.
Testing Library
Consultas y eventos que imitan a una persona usando la página, en cualquier framework.
- Creado por:
- Kent C. Dodds
- Versiones:
- React, Angular, Vue y más
Testing Library parte de un principio: cuanto más se parezcan tus pruebas a la forma en que se usa tu software, más confianza te darán. Por eso no da acceso al estado interno: buscas los elementos como lo haría una persona o un lector de pantalla.
Sus consultas tienen un orden de preferencia. La primera es getByRole, que busca por el rol accesible y el nombre, como en getByRole('button', { name: 'Entrar' }); le siguen getByLabelText y getByText, y getByTestId queda como último recurso. Las variantes queryBy… devuelven null si no encuentran nada y las findBy… esperan a que el elemento aparezca. Para interactuar, userEvent simula eventos completos, como teclear letra a letra.
El núcleo, DOM Testing Library, tiene versiones para React, Angular, Vue, Svelte y otros, con la misma API. Suele combinarse con jest-dom, que añade comprobaciones como toBeVisible() o toBeDisabled() y funciona también con Vitest.
Úsalo cuando
En casi cualquier prueba de componente, sea cual sea el framework. Si una consulta por rol no encuentra tu elemento, a menudo es la pista de un problema de accesibilidad.
Ten en cuenta
Evita getByTestId salvo que no haya alternativa: el atributo no lo ve nadie, así que la prueba no detecta si cambian el texto o el rol. Y no olvides el await con userEvent, porque sus acciones son asíncronas.
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { Login } from "./login";
it("pide la contraseña si se envía vacía", async () => {
const user = userEvent.setup();
render(<Login />);
await user.type(screen.getByLabelText("Correo"), "ana@ejemplo.com");
await user.click(screen.getByRole("button", { name: "Entrar" }));
expect(screen.getByRole("alert")).toHaveTextContent("Escribe tu contraseña");
});Interactivo
Selectores que sobreviven a un refactor
Elige cómo busca la prueba «el botón de enviar» y aplica cambios al formulario. Mira qué consultas siguen encontrándolo y cuáles fallan, y por qué.
<div class="tarjeta">
<form>
<label class="campo">Correo
<input type="email">
</label>
<label class="campo">Contraseña
<input type="password">
</label>
<button class="btn btn-link" type="button">Mostrar contraseña</button>
<button class="btn btn-primary" type="submit" data-testid="submit">Entrar</button>
</form>
</div>Encuentra el botón como lo haría un lector de pantalla: por su rol y su nombre. Los cambios de clases o de estructura no le afectan.
Las herramientas de cada framework
Cada framework ofrece además sus propias utilidades de prueba, que dan acceso a lo que es específico de él: la inyección de dependencias y la detección de cambios de Angular, los eventos y los slots de Vue… Puedes usarlas solas o combinarlas con Testing Library.
Angular Testing Utilities
TestBed, ComponentFixture y harnesses: las herramientas oficiales para probar en Angular.
- Paquete:
- @angular/core/testing
- Harnesses:
- @angular/cdk/testing
TestBed crea un entorno de pruebas con su propio inyector de dependencias: indicas el componente y los proveedores que necesita, y TestBed.createComponent() devuelve un ComponentFixture, que da acceso al componente (componentInstance) y a su DOM (nativeElement). detectChanges() lanza la detección de cambios y whenStable() espera a que terminen las tareas pendientes; en las aplicaciones sin zone.js de las versiones recientes, se recomienda esperar con await fixture.whenStable().
Para las peticiones HTTP, provideHttpClientTesting() y HttpTestingController interceptan las llamadas: compruebas qué se pidió y respondes con datos falsos, sin red. Y los component harnesses del CDK ofrecen una API estable para manejar componentes, como los de Angular Material, sin depender de su HTML interno: await boton.click() sigue funcionando aunque cambie su estructura.
Úsalo cuando
En cualquier proyecto Angular. Combínalas con Testing Library para Angular si quieres consultas por rol, o con los harnesses si usas componentes de Material.
Ten en cuenta
Es fácil acabar probando detalles internos a través de componentInstance. Siempre que puedas, interactúa con el DOM y comprueba lo que se ve, como harías con Testing Library.
import { TestBed } from "@angular/core/testing";
import { provideHttpClient } from "@angular/common/http";
import { HttpTestingController, provideHttpClientTesting } from "@angular/common/http/testing";
import { ListaProductos } from "./lista-productos";
it("pinta los productos que llegan de la API", () => {
TestBed.configureTestingModule({
imports: [ListaProductos],
providers: [provideHttpClient(), provideHttpClientTesting()],
});
const fixture = TestBed.createComponent(ListaProductos);
const http = TestBed.inject(HttpTestingController);
fixture.detectChanges(); // arranca el componente y lanza la petición
http.expectOne("/api/productos").flush([{ nombre: "Taza" }]);
fixture.detectChanges(); // vuelve a pintar con los datos
expect(fixture.nativeElement.textContent).toContain("Taza");
});Vue Test Utils
La librería oficial de Vue para montar componentes y manejarlos en las pruebas.
- Paquete:
- @vue/test-utils
- Runner habitual:
- Vitest
mount() monta un componente con sus props, slots y plugins y devuelve un «wrapper» con métodos para explorarlo: find() busca un elemento con un selector, text() devuelve su texto y trigger('click') dispara un evento. trigger devuelve una promesa: usa await para que Vue actualice el DOM antes de comprobar nada.
shallowMount() sustituye los componentes hijos por versiones vacías (stubs), útil para probar un componente sin arrastrar a todos sus descendientes. Y emitted() te dice qué eventos ha emitido el componente. Lo habitual es usarla con Vitest, el runner que recomienda Vue.
Úsalo cuando
En proyectos Vue, sobre todo cuando necesitas comprobar eventos emitidos, slots o stubs. Para consultas más centradas en el usuario existe Testing Library para Vue, construida encima de ella.
Ten en cuenta
Con shallowMount la prueba no ve cómo se comportan los hijos reales, así que puede pasar aunque la pantalla esté rota: prefiere mount. Y buscar por clases CSS con find('.btn') tiene los mismos problemas que has visto en el simulador.
import { mount } from "@vue/test-utils";
import { it, expect } from "vitest";
import Contador from "./Contador.vue";
it("suma uno al hacer clic y avisa al padre", async () => {
const wrapper = mount(Contador, { props: { inicial: 2 } });
await wrapper.find("button").trigger("click");
expect(wrapper.text()).toContain("3");
expect(wrapper.emitted("cambio")?.[0]).toEqual([3]);
});Enzyme (histórico)
La librería de pruebas para React de Airbnb, que dominó antes de Testing Library.
- Creado por:
- Airbnb
- Estado:
- sin mantenimiento
Enzyme, creado por Airbnb, fue durante años la forma estándar de probar componentes de React. Su función estrella era el shallow rendering: pintar un componente sin sus hijos. Permitía además leer y cambiar el estado interno (state(), setState()) y buscar elementos por selectores CSS.
Ese acceso a los detalles internos era también su problema: las pruebas se rompían al refactorizar aunque nada cambiara para el usuario. Además, Enzyme necesitaba un adaptador para cada versión de React; el último oficial fue el de React 16 y no funciona con las versiones modernas. Hoy está sin mantenimiento y Testing Library ocupó su lugar.
Úsalo cuando
Solo si mantienes un proyecto antiguo que lo usa, y con la idea de migrar sus pruebas a Testing Library poco a poco.
Ten en cuenta
No lo uses en proyectos nuevos. Si heredas pruebas de Enzyme, migrarlas es también una buena ocasión para dejar de probar el estado interno.
import { shallow } from "enzyme";
it("cambia el estado al hacer clic", () => {
const wrapper = shallow(<Contador />);
wrapper.find(".btn-sumar").simulate("click");
// Comprueba un detalle interno, no lo que ve el usuario
expect(wrapper.state("cuenta")).toBe(1);
});Interactivo
Cuatro formas de probar componentes
Activa o desactiva opciones para compararlas cara a cara.
| Característica | Testing Library | Angular Testing Utilities | Vue Test Utils |
|---|---|---|---|
| Frameworks | React, Angular, Vue, Svelte y más | Angular | Vue |
| Cómo buscas elementos | Por rol, etiqueta o texto, como una persona | Con nativeElement, debugElement o harnesses | Con selectores en find() y componentes en findComponent() |
| Acceso a detalles internos | No, a propósito | Sí, con componentInstance | Sí: props, eventos emitidos y stubs |
| Estado | Muy activa | Oficial | Oficial |
| Úsalo para | Probar el comportamiento visible | Inyección de dependencias, HTTP y detección de cambios | Eventos, slots y stubs de Vue |
Ponlo en práctica
Saber qué es cada cosa es la mitad del trabajo. La otra mitad es reconocer cuándo usarla.
Interactivo
¿Qué usarías?
Lee cada situación y elige la opción que mejor la resuelve. Fallar aquí no cuenta: es para practicar.
Quieres comprobar que tu formulario de React muestra «Escribe tu contraseña» cuando se envía vacío, buscando los elementos como lo haría un lector de pantalla.
Para recordar
- Una prueba de componente pinta el componente, interactúa con él como una persona y comprueba lo que se ve, no el estado interno.
- En Testing Library, prefiere
getByRolecon su nombre;getByTestIdes el último recurso. - Si una consulta por rol no encuentra tu elemento, probablemente tienes un problema de accesibilidad.
- Angular aporta TestBed,
HttpTestingControllery los harnesses; Vue Test Utils,mount,triggeryemitted; Enzyme es historia.
Comprueba lo aprendido
Cuestionario
Responde las 5 preguntas para completar la lección.
1.¿Qué consulta de Testing Library es la preferida para encontrar un botón?
2.Sustituyes un
<button>por un<div>con estilos y unonClick. ¿Qué consulta falla, y por qué es bueno que lo haga?3.En Angular, ¿para qué sirve
HttpTestingController?4.En Vue Test Utils, ¿por qué escribes
await wrapper.find('button').trigger('click')?5.¿Por qué Enzyme quedó atrás?