Saltar al contenido principal

Cómo Testear Componentes React con IA: Prompts para Testing Library

10 prompts para testear componentes React y Next.js con IA: Testing Library, userEvent, mocking de hooks y tests de Server Components.

Fran Cobos 22 min de lectura 4322 palabras

Tabla de contenidos

Escribes el componente, renderizas con render(<Card />), metes un getByText('Hola') y el test pasa. Toca cuatro líneas de maquetación y sigue pasando. Bien. Tres semanas después alguien mete un formatearPrecio() en el componente y el test sigue pasando, pero en producción los clientes ven NaN €. El test nunca comprobó el precio.

Los tests de componentes tienen un problema distinto al de las APIs y al de los E2E: son tan fáciles de escribir mal que acabas con cientos de ellos que no comprueban nada. La IA acelera ese problema en lugar de resolverlo, porque el camino de menor resistencia para “testear un componente” es generar un getByText y un expect(true).

La buena noticia es que React Testing Library lleva años insistingiendo en lo correcto: renderiza el componente de verdad, en un DOM de verdad, y golpéalo como lo golpearía un usuario. Si le dices eso a la IA de forma explícita en cada prompt, los tests que genera son sorprendentemente buenos. Los prompts de esta guía hacen exactamente eso, y además cubren las dos zonas donde la gente se atasca: el mocking de hooks y los Server Components de App Router.

Si quieres la visión general de estrategia de testing antes de entrar al detalle, mírala en cómo testear código generado por IA.

Cinco normas que conviene tener claras antes de copiar un prompt

1. Nada de container.querySelector. Si necesitas bucear en el DOM con selectores CSS, es que no has encontrado el elemento como lo encontraría el usuario. Los query de Testing Library (getByRole, getByLabel, getByText, getByPlaceholderText) son la API, no querySelector.

2. get* es para lo que existe, query* es para lo que no. Si usas queryBy para asertar que algo NO está, bien. Si usas queryBy y esperas que exista, el fallo es un null que revienta con un mensaje inútil. Ese es exactamente el criterio para elegir entre las dos familias.

3. userEvent antes que fireEvent, siempre. fireEvent.click no cambia el estado de focus, no dispara onChange en la mayoría de casos y no emula el dirty flag del input. userEvent.click sí. Un test que pasa con fireEvent y falla con userEvent te acaba de encontrar un bug real o un test que nunca probó lo que creía.

4. Mockea la red, no el hook. Si tu componente hace fetch, usa MSW. Si usas vi.mock('axios') estás probando que tu componente llama a axios con los argumentos correctos, no que muestra los datos. Solo mockea un hook cuando estés probando el hook en sí.

5. Un test, un comportamiento. Si el nombre del test necesita la palabra “y”, probablemente estás probando dos cosas. Divídelo y cuando uno falle sabrás cuál de los dos se rompió.

1. Analizar el componente antes de testearlo

Antes de escribir ningún test, analiza este componente React y propón
un plan de testing.

Componente:
[pegar el componente completo, incluyendo tipos]

Ubicación en la app: [ruta, ej: app/checkout/PaymentStep.tsx]
Datos: [de dónde vienen: props, hook propio, fetch, contexto]
Posibles dependencias: [hook de auth, librería de fechas, i18n]

Quiero cuatro salidas:

1. CATÁLOGO DE COMPORTAMIENTOS: la lista de todo lo que el usuario
   puede hacer o percibir en este componente. Cada comportamiento
   con su test correspondiente. No incluyas Pureza de renderizado sin
   efecto observable.

2. MAPA DE ESTADOS: qué estados distinctivos tiene (cargando, vacío,
   error, éxito, deshabilitado) y qué los dispara.

3. QUERY DE TESTING LIBRARY APROPIADO para cada elemento clave, con
   prioridad en getByRole > getByLabel > getByText > data-testid.

4. DEPENDENCIAS A MOCKEAR y por qué: para cada una, dime si lo
   correcto es MSW, vi.mock del módulo, o no mockear nada.

Después de ese análisis, NO escribas los tests todavía. Espera a que
lo confirme.

Por qué funciona: separar el análisis de la generación evita la ráfaga de veinte tests redundantes. El paso 4 es especialmente útil: la IA distingue mal entre lo que se debe mockear y lo que no, y por eso acaba con suites que pasan y no prueban nada.

2. Generar los tests de un componente

Genera los tests unitarios para este componente con React Testing Library.

Componente:
[pegar el componente]

Stack de testing:
- Vitest (vi.mock, vi.fn)
- @testing-library/react (v16)
- @testing-library/user-event (v14)
- @testing-library/jest-dom (matchers importados en el setup)
- jsdom como environment

Reglas no negociables:
1. userEvent.setup() en cada beforeEach, con advanceTimers si uso fake timers.
2. Nada de fireEvent.
3. Aserta siempre sobre algo visible en pantalla: texto, rol, estado
   deshabilitado, atributo aria. Nunca sobre props internas ni sobre
   variables del módulo.
4. Un test = un comportamiento. Nombra los tests con el
   formato "cuando [acción], entonces [resultado esperado]".
5. Usa waitFor solo si hay async real detrás. Si esperas a un elemento
   que ya está, estás escribiendo un test malo.
6. Nada de snapshot tests salvo que te pida explícitamente el snapshot
   de un fragmento concreto (no del componente entero).
7. Incluye el beforeEach de render, pero no metas estado global
   compartido entre tests.

Para cada test, antes del código, escribe una línea con el bug real
que detectaría si el componente se rompiera. Si no puedes escribir
esa línea, no escribas el test.

Por qué funciona: la regla 8 del prompt (la línea sobre qué bug detecta cada test) es el filtro de calidad más efectivo que conozco. Cuando la IA tiene que justificar para qué sirve cada test, deja de generar los triviales.

Ejemplo real de test generado con esa disciplina:

// Detecta el bug: si el total se formatea con toFixed sobre un valor
// undefined tras cargar el carrito, se renderiza "NaN €".
test('muestra el total formateado en euros tras cargar el carrito', async () => {
  const user = userEvent.setup();
  server.use(
    http.get('*/api/cart', () =>
      HttpResponse.json({ items: [{ price: 1299 }] })
    )
  );

  render(<CartSummary />);

  expect(await screen.findByText('12,99 €')).toBeInTheDocument();
});

3. getByRole y el nombre accesible: el prompt de accesibilidad

Mi componente se testea con selectores por texto y quiero pasarlo a
getByRole, pero varios elementos fallan porque no tienen nombre
accesible.

Componente:
[pegar el componente]

Genera una tabla con:
| Elemento | getByRole que quiero usar | Por qué falla ahora | Cambio exacto en el componente |

Recordatorio de los roles que necesito saber usar:
- button, link, heading (con level), textbox, checkbox, radio,
  combobox, list, listitem, table, row, cell, alert, status, dialog,
  tab, tablist, option, img (con nombre), navigation, banner, main,
  form, region, progressbar, slider, switch

Y estos patrones:
- Un input necesita <label htmlFor> o aria-label para tener nombre.
- Un div con onClick y role="button" necesita tabIndex={0} y manejo
  de Enter/Espacio para ser usable con teclado.
- Un icono dentro de un botón necesita aria-hidden="true" para no
  contaminar el nombre accesible del botón.
- aria-live="polite" en el contenedor de un mensaje de error o de éxito
  para que un lector de pantalla lo anuncie.

Regla: propón el cambio de HTML del componente SIEMPRE que exista una
solución equivalente sin aria-label. El HTML semántico gana.

Después de la tabla, dame el archivo del componente ya corregido y el
test con getByRole para cada uno de esos elementos.

Por qué funciona: cada vez que obligo a la IA a justificar un selector, sale un problema de accesibilidad. Los div onClick que no responden al teclado y los inputs sin label salen solos. Es un prompt que mejora el producto mientras escribe los tests.

4. Interacciones con userEvent

Genera los tests de interacción de usuario para este componente,
usando userEvent correctamente.

Componente:
[pegar]

Interacciones que necesito cubrir:
[describir: escribir en un input, seleccionar en un desplegable,
marcar un checkbox, subir un fichero, hacer drag and drop, usar un
teclado para abrir un modal]

Para cada una quiero el test con userEvent y el detalle de por qué
fireEvent no serviría:

- await user.type(input, 'texto') en lugar de fireEvent.change,
  porque emula keydown, keypress, input y keyup, y solo entonces
  React actualiza el estado controlado.
- await user.clear() antes de escribir de nuevo, para no acumular
  texto en el input.
- await user.selectOptions() para <select> y para los listboxes
  custom implementados con ARIA.
- await user.upload() para ficheros: mockea el objeto File y comprueba
  que el onChange recibe el File, no el path.
- await user.keyboard('{Escape}') y '{Tab}' para probar navegación por
  teclado, y assert de document.activeElement.
- await user.pointer() para los casos de hover y pointer events.

Un caso concreto que siempre falla con fireEvent y quiero bien hecho:
los checkboxes que deshabilitan otros campos. Al desmarcar un
checkbox, los inputs dependientes deben deshabilitarse. Con
fireEvent.click eso no se dispara de forma fiable.

Por qué funciona: nombrar explícitamente el caso de los checkboxes que deshabilitan otros campos da como resultado tests que detectan bugs muy reales de UI, porque el estado disabled depende del ciclo completo del evento y fireEvent lo simula a medias.

5. Mockear hooks correctamente (y cuándo no hacerlo)

Tengo componentes que usan hooks y los tests se están volviendo
frágiles con el mocking.

Hooks usados en el componente:
[pegar el componente con los imports de los hooks]
Implementación del hook useAuth:
[pegar]

Genera tres versiones del mock y dime cuál usar en cada caso:

1. MOCK DE MÓDULO con vi.mock: para cuando el hook es una dependencia
   externa y lo que me importa es SU VALOR, no su comportamiento.
   Dame el código con el tipo de retorno completo, que devuelva un
   objeto con TODAS las claves de mi interfaz de retorno, tipado.

2. MOCK CON TESTID / DATA ATTRIBUTE: para cuando quiero probar que el
   componente renderiza el valor del hook, no el hook en sí.

3. WRAPPER PROVIDER: cuando el hook depende de un contexto. Crea un
   AuthContext.Provider con el valor de test y un helper
   renderWithAuth(ui, { user, isLoading }) para que los tests sean
   legibles.

Regla dura: si estoy testeando el propio hook, no mockees el hook.
Extrae su lógica a una función pura y testéala. Si no es posible,
testéalo con renderHook de @testing-library/react y aserta sobre
act y result.current.

Incluye además el caso del loading: el hook devuelve isLoading true
y mi componente debe mostrar el skeleton. Ese test casi nunca existe
y siempre hace falta.

Por qué funciona: nombrar las tres estrategias y dejar que la IA decida cuál encaja en cada caso evita el patrón de “mockear todo lo que se pueda”, que produce tests que pasan mientras el hook real está roto. La regla dura sobre no mockear lo que estás testeando evita la circularidad.

6. Mockear la red con MSW en lugar de mockear fetch

Mi componente hace llamadas a la API y quiero testearlo sin
mockear el fetch.

Componente:
[pegar, con la lógica de carga]

Configuración actual de MSW: [pegar el server.ts o el handlers.ts]
msw v2 con http.get/http.post del módulo 'msw'.

Genera los handlers por defecto (éxito con datos de ejemplo) en
tests/mocks/handlers.ts y los overrides por test con server.use(...).

Casos a cubrir:
1. Carga con éxito: assert del contenido renderizado con
   findBy* (que espera de forma asíncrona).
2. Respuesta vacía: assert del estado vacío del componente.
3. Error 500: assert del mensaje de error que ve el usuario, en el
   formato exacto que muestra mi componente.
4. Error 422 con errores de campo: mi componente debe mapear
   response.error.errors al formulario.
5. Respuesta lenta: uso delay() de MSW para verificar que el
   skeleton aparece. El test debe assert que el contenido aún NO
   está, para detectar flashes de "cargando" mal implementados.
6. Dos peticiones simultáneas del mismo componente (por ejemplo un
   endpoint llamado dos veces por un useEffect mal escrito):
   assert del número de llamadas para detectar el doble fetch.

Los datos de respuesta deben estar en factories separadas, con un
tipo que coincida con lo que devuelve mi API. Si el tipo no existe,
dímelo y propón crearlo a partir del response real.

Por qué funciona: MSW intercepta en la capa de red, así que el componente ejecuta su código de error real, no un return inventado por el mock. El caso 6 (doble fetch por un useEffect con dependencias mal puestas) es un bug de rendimiento carísimo y que nadie detecta a ojo.

7. Estados de carga, error y vacío

Genera los tests de los estados no felices de este componente, que
ahora mismo no están cubiertos.

Componente:
[pegar el componente y su lógica de estado]

Necesito, como mínimo, un test para cada uno de estos estados:
[ ] Inicial sin datos
[ ] Cargando (skeleton/spinner visible)
[ ] Cargando con datos previos (al refrescar, no debe desaparecer
    todo el contenido: es el "stale while revalidate")
[ ] Éxito con datos
[ ] Éxito con lista vacía (NO es lo mismo que cargando)
[ ] Error de red con mensaje y botón de reintentar
[ ] Error de validación con errores por campo
[ ] Estado deshabilitado del formulario durante el envío

Para el estado de "cargando con datos previos", que es el que más
se olvida, necesito dos responses encadenadas en MSW: la primera
con datos, la segunda con datos distintos, y assert de que la UI
mantiene los datos antiguos visibles mientras llega la nueva respuesta.

En cada test, dos asserts mínimos: el estado visible Y la ausencia
del estado anterior. Nunca basta con comprobar que algo aparece.

Por qué funciona: exigir siempre el assert positivo y el negativo (“aparece X” y “ya no aparece Y”) es lo que separa un test de un getBy decorativo. Y el caso de “cargando con datos previos” detecta el flash de contenido vacío, que es el bug de UX más reportado en dashboards.

8. Tests de App Router y Server Components

Tengo una app de Next.js con App Router y necesito testear la capa de
servidor separada de la de cliente.

Estructura:
[pegar: app/checkout/page.tsx, app/checkout/actions.ts,
components/ServerCart.tsx, components/ClientPayForm.tsx]

Stack: Next.js [versión], React [19.x], Vitest, RTL v16 con soporte de
Server Components, MSW para la red.

Genera:

1. PAGE COMPONENT (Server Component): el test debe renderizarlo como
   async server component, no como componente de cliente normal. Dame
   el patrón exacto con RTL v16 y RSC support, incluyendo cómo se
   hace el await del componente async antes de asertar.

2. ACTIONS (Server Actions): test unitario de la action aislada,
   mockeando el repositorio de datos y verificando los casos de
   éxito, validación con useActionState y error inesperado.

3. LAYOUT DE SEPARACIÓN: identifique qué parte del árbol es cliente y
   qué parte es servidor, y dime qué de cada una se puede testear de
   verdad. Lo que es servidor necesita MSW o mock del data layer; lo
   que es cliente, RTL normal.

4. Next/navigation mockeado: useRouter, usePathname, useSearchParams
   con el patrón correcto en v6 de msw, o con un mock manual si es
   más limpio. Nada de intentar renderizar un router de verdad.

5. Loading y error: los archivos loading.tsx y error.tsx también se
   renderizan en RTL. Si mi error.tsx tiene un reset(), testéalo.

Si algo de esto NO es posible de testear bien, dímelo claramente
en vez de inventar un patrón. Prefiero saber que una capa concreta
necesita Playwright en lugar de un test que no prueba nada.

Por qué funciona: los Server Components son donde la IA alucina más: te genera un render(<Page />) sobre un componente async y un test que pasa porque solo aserta que no explota. Pedirle explícitamente que señale lo que no es testeable es lo que te ahorra los tests de mentira.

9. Coverage de componentes: encontrar los huecos reales

Tengo esta suite de tests de componentes y quiero saber qué NO está
cubierto de verdad.

Componentes con sus tests:
[pegar componente + su .test.tsx]

Ejecuta mentalmente esta revisión y dame los huecos:

1. Ramas de renderizado no ejercitadas: ¿hay algún estado que el
   componente pueda tomar y ningún test lo alcance?
2. Callbacks: ¿cada onClick, onChange y onSubmit se dispara alguna
   vez en algún test con argumentos válidos?
3. Props condicionales: ¿se prueba cada valor de enum o discriminante
   que el componente acepta?
4. Interacciones encadenadas: ¿hay tests que hacen una acción y
   comprueban el efecto de esa acción, o solo acciones aisladas?
5. Accesibilidad: ¿el componente tiene elementos interactivos que no
   se localizan por rol en ningún test?
6. Errores: ¿hay algún try/catch o estado de error en el componente
   sin ningún test que lo exercite?

Para cada hueco, escribe el test que lo cubre, con la misma
disciplina del prompt 2.

Y un apunte honesto: dime qué partes del componente NO merece la
pena testear (renderizado estático, markup puramente cosmético) para
que no acabemos persiguiendo un 100% de líneas sin valor.

Por qué funciona: el punto 6 es el que más rendimiento da por minuto invertido, porque los catch vacíos o mal implementados son la fuente número uno de errores silenciosos en producción. Y la petición explícita de “dime qué NO merece la pena” evita la caza del pointless coverage.

10. Tabla comparativa de queries: elegir bien en cada caso

Voy a escribir los tests de este componente y quiero que cada elemento
se localice con el query correcto.

Componente:
[pegar]

Genera una tabla de decisión con TODOS los elementos interactivos y
de contenido, y para cada uno:
| Elemento | Query recomendado | Por qué | Query que NO debo usar |

Reglas estrictas:
- Elemento con rol semántico (button, link, heading) → getByRole
- Input con etiqueta visible → getByLabelText
- Input con solo placeholder → getByPlaceholderText (y marca que
  es frágil si el placeholder puede cambiar)
- Texto estático de la interfaz → getByText con exact cuando sea
  una coincidencia única
- Contenido dinámico (nombres, fechas, precios) → getByText con
  regex, nunca con el literal
- Elemento que NO debe existir → queryBy... (y explica que getBy
  lanzaría aquí, que es justo lo que quiero en un caso negativo)
- Varios elementos con el mismo texto → getAllBy... o getBy... con
  el selector exacto; explica el caso donde necesito distinguir por otro atributo

Incluye además los "matchers" de jest-dom que son más útiles en cada
caso: toBeInTheDocument, toBeVisible, toBeDisabled, toHaveValue,
toHaveTextContent, toHaveAttribute, toBeChecked, toHaveFocus.

Termina con un test de ejemplo por cada categoría de query usada
en la tabla, para que vea el patrón completo.

Por qué funciona: convertir la elección de query en tabla explícita quita la ambigüedad de una vez. Y la distinción entre getBy y queryBy con la explicación de por qué el error es deseable en el caso negativo evita el clásico queryBy mal usado que devuelve null y revienta tres líneas después.

Anti-patrones: los que la IA escribe sin que se lo pidas

❌ Snapshot del componente entero. El snapshot pasa siempre la primera vez y falla siempre en el siguiente refactor, sin que nada importante haya cambiado. Nadie lo actualiza y el test deja de decir nada.

// ❌ Sale verde el primer día, rojo para siempre después
it('renderiza bien', () => {
  const { container } = render(<UserCard user={mockUser} />);
  expect(container.firstChild).toMatchSnapshot();
});

✅ Assertions sobre lo que el usuario ve y hace. Esas sobreviven a los refactors de markup, que es justo lo que refactorizas cada tres meses.

// ✅ Fallo solo cuando el comportamiento cambia
it('muestra el nombre y el rol del usuario', () => {
  render(<UserCard user={mockUser} />);

  expect(screen.getByRole('heading', { name: 'Ana Ruiz' })).toBeInTheDocument();
  expect(screen.getByText('Administradora')).toBeInTheDocument();
  expect(screen.getByRole('button', { name: 'Editar perfil' })).toBeEnabled();
});

❌ fireEvent para simular escritura. Es el patrón por defecto de cualquier ejemplo de testing que se haya escrito en 2018, y la IA lo reproduce sin dudarlo.

// ❌ React ni se entera del cambio en el input
fireEvent.change(screen.getByLabelText('Email'), {
  target: { value: 'ana@test.com' }
});

✅ userEvent.type, que emite la secuencia completa de eventos y actualiza el estado controlado de React.

// ✅ Es lo que pasa de verdad cuando una persona escribe
await user.type(screen.getByLabelText('Email'), 'ana@test.com');

❌ Mockear useState para forzar un valor. Suena elegante y rompe el componente entero: la inicialización de useState no pasa por el mock igual que un update, y los callbacks que dependan del valor se quedan desincronizados.

✅ Mockear el módulo del hook devolviendo la interfaz completa, o mejor, mockear la red con MSW y dejar que el hook se ejecute de verdad.

❌ Envolver todo en un QueryClientProvider/ThemeProvider custom sin exportarlo como helper. Cada test repite las mismas diez líneas de wrapper y nadie sabe qué aporta cada una.

✅ Un helper renderWithProviders con valores por defecto sensatos y capacidad de sobrescribirlos por test.

Query de Testing Library: elegir bien desde el primer día

Necesito comprobarQuery recomendadaEvitarPor qué
Botón, enlace, headinggetByRole('button', { name })getByClassNameEs lo que ve un lector de pantalla
Campo de formulariogetByLabelText('Email')getByPlaceholderTextEl placeholder puede cambiar o traducirse
Texto visible estáticogetByText('Hola') con exactgetByText(/hola/i) sin másexact: true evita falsos positivos
Texto dinámico (nombre, fecha)getByText(/Ana Ruiz/)getByText('Ana Ruiz') literalEl valor cambia entre tests
Elemento que NO existequeryBy...getBy...getBy lanza excepción y no asertas
Elemento deshabilitadogetByRole('button').toBeDisabled()Comprobar la clase CSSEl comportamiento es el que importa
Valor de un inputtoHaveValue('texto')Leer input.value a manoEl matcher da el error descriptivo
Contenido accesible de un componentetoHaveAccessibleName()Leer el aria-label del DOMComprueba lo que anuncia el lector de pantalla

Errores frecuentes al testear con IA

“Unable to find an accessible element with the role button”. Es el error más común y casi siempre significa una de dos cosas: el rol no existe porque el elemento es un div, o el nombre accesible difiere porque hay texto oculto o un icono dentro. El mensaje de Testing Library incluye los roles disponibles: úsalo como mapa, no lo ignores.

El componente no renderiza nada y el test falla con “container is empty”. Casi siempre es un error en la importación, un componente que lanza en el módulo que importas, o un Server Component renderizado como si fuera de cliente. Comprueba la consola antes que los locators.

Cannot log after tests are done. Did you forget to wait for something async. Hay un fetch sin await dentro de un act. Solución: envuelve el render en await act(...) si actualiza estado asíncrono, o usa findBy* en lugar de getBy* para lo asíncrono.

Los tests pasan los primeros 40 y luego todos fallan a la vez. Test pollution: estado compartido entre tests. cleanup() de RTL se ejecuta solo, pero los mocks de módulos (vi.mock factories) y los singletons (un QueryClient creado a nivel de módulo) no se limpian. Crea el cliente dentro de un helper por test.

Todos los tests dan el mismo error y no sabes por dónde. Ejecuta con it.only sobre uno y pon un screen.debug() temporalmente. La versión recortada del DOM en el fallo te dice el 90% de las veces qué selector necesitas.

El componente depende de una variable de window que no existe en jsdom. window.matchMedia, IntersectionObserver, ResizeObserver. Crea un setup file con los polyfills mínimos una vez y olvídate. No los mockees en cada test.

Los tests son lentos en local. Si un test tarda más de 200 ms, casi siempre hay un waitFor con timeout largo esperando algo que nunca llega. Revisa que los tests asíncronos usen findBy* en lugar de getBy* seguido de waitFor.

El flujo completo

   ┌──────────────────────────────┐
   │ Prompt 1 → analizar          │  Comportamientos + estados
   │ el componente                │
   └───────────────┬──────────────┘
                   ▼
   ┌──────────────────────────────┐
   │ Prompt 3 → roles y ARIA      │  Accesibilidad + selector bueno
   └───────────────┬──────────────┘
                   ▼
   ┌──────────────────────────────┐
   │ Prompt 2 → tests base        │  Render + comportamiento
   └───────────────┬──────────────┘
                   ▼
   ┌──────────────────────────────┐
   │ Prompt 10 → tabla de queries │  getBy vs queryBy correcto
   └───────────────┬──────────────┘
                   ▼
   ┌──────────────────────────────┐
   │ Prompt 4 → userEvent         │  Interacciones reales
   └───────────────┬──────────────┘
                   ▼
   ┌──────────────────────────────┐
   │ Prompt 6 → MSW               │  Datos, errores, lentitud
   └───────────────┬──────────────┘
                   ▼
   ┌──────────────────────────────┐
   │ Prompt 7 → estados no felices│  Loading, vacío, error
   └───────────────┬──────────────┘
                   ▼
   ┌──────────────────────────────┐
   │ Prompt 5 → hooks             │  Solo lo que hay que mockear
   └───────────────┬──────────────┘
                   ▼
   ┌──────────────────────────────┐
   │ Prompt 8 → RSC / App Router  │  Capa servidor separada
   └───────────────┬──────────────┘
                   ▼
   ┌──────────────────────────────┐
   │ Prompt 9 → huecos reales     │  Ramas sin cubrir
   └──────────────────────────────┘

Conclusión

Testear componentes con IA funciona muy bien cuando los prompts llevan el peso de la disciplina que la IA no tiene por defecto: sin “un test, un comportamiento”, sin “no uses fireEvent” y sin “dime qué bug detectaría este test”, lo que obtienes es una carpeta llena de snapshots que nadie se atreve a tocar. Con esas reglas, la salida es código que parece escrito por alguien que lleva años con Testing Library.

Esta pieza es la capa intermedia de la pirámide: por debajo están los prompts para testear APIs REST con Supertest y por encima los prompts para tests E2E con Playwright. Los tres cubren una aplicación completa, y si quieres saber cómo encaja todo esto con el código que genera la IA en el editor, está en cómo testear código generado por Copilot y Cursor.

Para el contexto de herramientas, mi ranking de los mejores modelos de IA para programar tiene una sección específica sobre qué modelos rinden mejor en tareas de testing, y en la comparativa Cursor vs Copilot vs Windsurf comparo cómo se comportan generando tests. Si el problema es el contexto que se te llena, la guía de context window y cómo aprovecharlo te resuelve el caso de pegarle un componente de 400 líneas al prompt.

Para el stack que muchas veces acompaña al componente, tengo guía de webhooks de Stripe en Next.js y de API Node.js protegida con JWT, las dos con las piezas que luego acabarás testeando aquí. Y si quieres el prompt sin escribirlo, mi generador interactivo incluye presets de testing.

Fran Cobos

Fran Cobos

Desarrollador Full Stack especializado en IA aplicada, automatización y desarrollo web. Escribo sobre herramientas, tutoriales y casos reales para programadores.

¿Necesitas desarrollo a medida?

Apps web, IA, módulos ERP — cuéntame tu proyecto.