Mejores prácticas de testing
Un resumen condensado de las 25 mejores prácticas más importantes extraídas de todas las páginas de esta sección.
Busca en todas las páginas de la documentación
Un resumen condensado de las 25 mejores prácticas más importantes extraídas de todas las páginas de esta sección.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
getByRole → getByLabelText → getByText → getByTestId) para que las aserciones coincidan con el árbol de accesibilidad; usar getByTestId primero acopla los tests a la implementación y pasa por alto regresiones de a11y.getBy* lanza una excepción cuando no hay coincidencias y findBy* reintenta, así que solo queryBy* es seguro para aserciones de «no debería existir» - usar getBy* para ausencia produce un error confuso de «no encontrado» en lugar de una aserción fallida clara.within(listItem) (o un aria-label con nombre único) para apuntar exactamente a la fila que quieres en lugar de la primera coincidencia de la página.userEvent (v14+) devuelve una Promise, así que olvidar await hace que la siguiente aserción se ejecute antes de que el evento se procese y convierte tests fallidos en falsos positivos silenciosos - siempre await userEvent.click(button).userEvent.type despacha la secuencia completa focus → keydown → keypress → input → keyup, que es lo que escuchan los componentes y validadores reales; fireEvent.change solo establece el valor y omite los eventos de pulsación de tecla de los que dependen bibliotecas como React Hook Form.createJestConfig de next/jest para obtener gratis las transformaciones SWC, los mocks de módulos CSS y la resolución de env/alias consciente de next.config.js; pon los matchers de jest-dom en setupFilesAfterSetup (no en setupFiles) o las extensiones de expect no estarán en el ámbito.@vitejs/plugin-react para JSX, environment: "jsdom", globals: true, una importación de @testing-library/jest-dom/vitest en el archivo de setup y "vitest/globals" en tsconfig.compilerOptions.types - si falta cualquiera, JSX o los tipos de matchers fallan en silencio.vi.mock() se eleva (hoist) por encima de los imports, así que las variables declaradas en el cuerpo del módulo son undefined dentro de la factory; usa vi.hoisted(() => ({ mockPush: vi.fn() })) y referencia esa ref compartida tanto desde la factory como desde tus tests.vi.clearAllMocks() (o jest.clearAllMocks()) en beforeEach; lo mismo aplica a vi.restoreAllMocks() para spies y server.resetHandlers() para MSW.findByRole / findByText cuando esperas que aparezca un elemento, y waitFor solo cuando la aserción no es sobre un elemento (p. ej., un contador de llamadas de efecto secundario) - findBy encapsula el reintento y ofrece mejores mensajes de error.waitFor sondea su callback cada ~50 ms, así que poner userEvent.click() o un spy de fetch dentro dispara el evento repetidamente y corrompe el state; haz la interacción fuera de waitFor y deja solo las aserciones dentro.server.resetHandlers() en afterEach para que las sobrescrituras por test no se filtren al siguiente, y mantén onUnhandledRequest: "error" para que cualquier endpoint olvidado salga a la superficie de inmediato en lugar de enmascararse como un cuelgue misterioso.useCartStore.setState({ items: [] }) (o getState().reset()) en beforeEach - de lo contrario, las mutaciones de un test presembran invisiblemente el siguiente.QueryClient nuevo con retry: false dentro de un helper renderWithProviders para cada test; un cliente compartido cachea respuestas entre tests y cuelga las suites en reintentos fallidos.role="alert" para que se anuncien al lector de pantalla y sean consultables como await screen.findAllByRole("alert"); recuerda que React Hook Form valida al enviar por defecto, así que aserciones de errores antes del envío no encuentran nada a menos que configures mode: "onChange".useActionState no puede ejecutar realmente una Server Action bajo jsdom, así que mockea el hook para devolver una tupla controlada [state, formAction, isPending] al probar la UI de error/pendiente del formulario - probar la acción real pertenece a un test de integración en Node/Vitest.renderHook, cualquier llamada síncrona que provoque una actualización de state necesita act(() => result.current.increment()) o obtienes una advertencia más un result.current obsoleto; con temporizadores falsos, avanza también dentro de act o pasa { shouldAdvanceTime: true }.result.current es una referencia viva que cambia después de cada renderizado, así que const { count } = result.current captura una instantánea que queda obsoleta - lee siempre result.current.count directamente en cada aserción.render() no acepta una Promise, así que prueba Server Components asíncronos llamándolos como funciones y esperando el JSX: const ui = await PostList({ id: "1" }); render(ui); - pasar el componente directamente lanza una excepción.cookies(), headers(), revalidatePath y revalidateTag lanzan excepciones fuera del contexto de petición de Next.js, así que sustitúyelos con vi.mock("next/headers", …) y vi.mock("next/cache", …) - también mockea notFound() para que lance un error centinela y await expect(fn()).rejects.toThrow() funcione.page.getByRole, getByLabel y getByText sobre selectores CSS y encapsúlalos en clases Page Object; selectores CSS como .btn-primary.mt-4 se rompen en cuanto alguien renombra una clase.expect() de Playwright reintentan automáticamente hasta 5 segundos, así que await expect(page).toHaveURL("/dashboard") reemplaza cada page.waitForTimeout(500) - las pausas fijas son la causa n.º 1 de tests E2E inestables.webServer en playwright.config.ts para arrancar Next.js automáticamente con reuseExistingServer: !CI, y habilita trace: "on-first-retry" para que las ejecuciones fallidas incluyan una línea de tiempo completa de DOM/red/consola que puedas abrir con playwright show-trace.setup más storageState: "e2e/.auth/user.json" para que cada test empiece autenticado sin pagar el coste del login, y registra mocks de page.route() antes de page.goto() - las rutas configuradas después de la navegación pierden la carga inicial de la página.Revisado por Chris St. John·Última actualización: 16 jul 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥