Ir al contenido
angel.library

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.

helper, schema o service → Vitest/node
Client Component → Testing Library
Route Handler importable → Request/Response
Server Action → servicio + integración del formulario
async Server Component, middleware y cache → Playwright
build, metadata y prerender → next build + E2E de producción
CapaHerramientaQué cubre
UnitarioVitestvalidadores, mappers, services, helpers
Componente clienteVitest + Testing Libraryinteracción, estados y accesibilidad local
Integración/E2EPlaywrightrouting, Server Components, Actions, cookies y navegador real

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.

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.

tests/home.spec.ts
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.

  • 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, error y not-found: navega a estados lentos, excepcionales y recursos inexistentes.

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.

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.

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:

  1. carga la ruta y conserva la versión;
  2. modifica la fuente;
  3. confirma si la ruta debe seguir fresca o cacheada;
  4. ejecuta la revalidación;
  5. 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.

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ó.

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.

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.