Testing en Next.js — unitario, componentes y E2E
No existe una sola capa de tests que cubra bien todo Next.js. Separa lógica pura, componentes cliente y flujos completos.
Ruta rápida
Sección titulada «Ruta rápida»helper, schema o service → Vitest/nodeClient Component → Testing LibraryRoute Handler importable → Request/ResponseServer Action → servicio + integración del formularioasync Server Component, middleware y cache → Playwrightbuild, metadata y prerender → next build + E2E de producción| Capa | Herramienta | Qué cubre |
|---|---|---|
| Unitario | Vitest | validadores, mappers, services, helpers |
| Componente cliente | Vitest + Testing Library | interacción, estados y accesibilidad local |
| Integración/E2E | Playwright | routing, Server Components, Actions, cookies y navegador real |
Extraer lógica antes de testear
Sección titulada «Extraer lógica antes de testear»Una Server Action debería coordinar autorización, validación y persistencia; la regla de negocio compleja conviene extraerla a una función testeable sin runtime de Next.
export function calcularTotal(items: { price: number; quantity: number }[]) { return items.reduce((total, item) => total + item.price * item.quantity, 0)}No extraigas una función solo para aumentar cobertura. Extrae reglas con nombre propio y deja una prueba de composición para demostrar que Next las invoca con identidad, datos y respuesta correctos.
Componentes asíncronos
Sección titulada «Componentes asíncronos»Los runners basados en jsdom no reproducen por completo el pipeline de React Server Components. Para páginas async, streaming, cookies(), navegación y límites loading/error, prefiere E2E. Los Client Components sí encajan bien en Testing Library.
La guía oficial de Next.js recomienda E2E para Server Components asíncronos que Vitest no soporta directamente. Evita mocks profundos del runtime que solo imitan tu interpretación de Next.
E2E mínimo
Sección titulada «E2E mínimo»import { expect, test } from "@playwright/test"
test("navega al detalle", async ({ page }) => { await page.goto("/productos") await page.getByRole("link", { name: /producto uno/i }).click() await expect( page.getByRole("heading", { name: /producto uno/i }) ).toBeVisible()})Prueba contra next build && next start al menos en CI: desarrollo no replica caché, optimizaciones ni errores de prerender de producción.
Referencia oficial: Testing.
Matriz específica de Next.js
Sección titulada «Matriz específica de Next.js»- Server Component con datos: prueba la función de acceso a datos y el flujo completo en Playwright.
- Client Component: Testing Library para interacción, estados, accesibilidad y eventos.
- Server Action: prueba validación y autorización fuera de React; confirma revalidación, redirect y errores con E2E.
- Route Handler: integración con
Request/Response, cookies, headers y status reales. loading,errorynot-found: navega a estados lentos, excepcionales y recursos inexistentes.
Route Handlers
Sección titulada «Route Handlers»Si el handler no depende de estado global de Next.js, invócalo con un Request real y afirma la respuesta completa.
import { GET } from "@/app/api/products/route"
it("valida el límite de resultados", async () => { const response = await GET( new Request("http://example.test/api/products?limit=0") )
expect(response.status).toBe(400) await expect(response.json()).resolves.toMatchObject({ code: "INVALID_LIMIT" })})Añade integración o E2E cuando intervengan cookies(), middleware, caché, región o runtime Edge, porque una llamada directa no reproduce toda la tubería.
Prueba método no permitido, content type, body inválido, autorización, status y ausencia de datos sensibles. Si el handler accede a base, usa una instancia aislada en lugar de mockear el repositorio en todos los casos.
Server Actions
Sección titulada «Server Actions»Separa la regla de negocio de la función marcada con 'use server'. Prueba validación, autorización y persistencia en el servicio; después usa E2E para confirmar el envío del formulario, el estado pendiente, el error visible, redirect y revalidatePath/revalidateTag.
Una acción es una frontera pública aunque solo la invoque tu interfaz. No confíes en que el formulario ocultó un campo: repite validación y autorización en servidor y agrega un caso de recurso ajeno.
Comprueba doble envío e idempotencia cuando la acción produce pagos, órdenes o mensajes. Un botón deshabilitado mejora UX, pero no evita dos requests directos.
Caché, revalidación y renderizado
Sección titulada «Caché, revalidación y renderizado»La caché depende del modo de ejecución y no queda demostrada por un mock de fetch. Usa un recurso de prueba que permita observar conteos o versiones:
- carga la ruta y conserva la versión;
- modifica la fuente;
- confirma si la ruta debe seguir fresca o cacheada;
- ejecuta la revalidación;
- confirma el nuevo contenido.
Prueba con build + next start; el dev server puede comportarse diferente. Aísla datos para que otro test no revalide la misma clave o path.
Middleware, proxy y redirects
Sección titulada «Middleware, proxy y redirects»Una función pura puede decidir la regla de acceso, pero una E2E confirma matcher, cookies, URL y redirect real. Incluye rutas públicas, privadas, assets y loops de redirección. No afirmes solo la URL final: comprueba que el contenido protegido no se filtró.
Aislar el entorno
Sección titulada «Aislar el entorno»Usa una base de datos de prueba y variables explícitas. No compartas una sesión entre tests paralelos. Prueba una vez contra next start con build de producción y conserva una suite rápida para desarrollo, porque el dev server puede ocultar problemas de caché, prerender o variables ausentes.
Errores que la suite debe detectar
Sección titulada «Errores que la suite debe detectar»Incluye acceso a un recurso ajeno, mutación sin sesión, cookie con atributos incorrectos, acción repetida, respuesta lenta y error del proveedor. Verifica el HTML y el comportamiento visible, no detalles internos del árbol de React. Un test de Next.js es más resistente cuando afirma lo que recibe el usuario.