Haces push de un pequeño cambio de UI antes del almuerzo. Parece inofensivo. Cambia la etiqueta de un botón, se simplifica un render condicional y un hook auxiliar incorpora una nueva rama. El pull request está limpio, la revisión es rápida y el deploy sale.
Una hora después, soporte informa de que el login dejó de funcionar en una plataforma. Web se ve bien. El shell de escritorio tiene una ruta de render obsoleta. El build móvil se comporta de forma diferente tras un cambio de estado asíncrono. Nadie lo detectó porque code tenía tests, pero no los tests correctos, y desde luego no un sistema fiable alrededor de esos tests.
Ese es el principal problema con las pruebas unitarias de React en equipos de producción. Escribir unas pocas pruebas que pasen no es difícil. Construir una suite que te siga protegiendo durante refactors, release trains, hotfixes y empaquetado multiplataforma es la parte difícil. Las apps de React no fallan porque un equipo olvidó cómo llamar render(). Fallan porque las pruebas derivan hacia detalles de implementación, el comportamiento asíncrono se ignora y la CI trata las pruebas como una casilla por marcar en lugar de una puerta de liberación.
La prueba de unidades moderna de React funciona cuando actúa como un sistema de seguridad. Retroalimentación rápida localmente. Verificaciones deterministas en la CI. Límites claros sobre qué pertenece a una prueba de unidades y qué no. Eso importa aún más cuando la misma base de código de React se distribuye a través de navegadores, contenedores Capacitor o shells de Electron.
Tabla de contenidos
- Unit testing en React: tu mejor red de seguridad
- Configurando tu entorno moderno de testing en React
- Escribir tests de componentes con sentido
- Testing de Custom Hooks y lógica de la aplicación
- Dominando técnicas avanzadas: mocking y async
- Mejorando la calidad y estrategia de tests
- Integrando tests en un pipeline de CI/CD multiplataforma
Unit testing en React: tu mejor red de seguridad
Los unit tests valen la pena cuando detectan ese error que jurabas que no podía pasar. En React, normalmente significa que un componente sigue renderizando, pero ha cambiado el comportamiento del que depende el usuario. Un botón deshabilitado se vuelve clickable. Un estado de carga nunca se limpia. Un mensaje de fallback desaparece tras un refactor. Esos fallos son pequeños en code y costosos en producción.
El testing en React cambió de forma importante cuando React Testing Library se convirtió en el modelo dominante para testear comportamiento en vez de internals, impulsando a los equipos hacia tests que reflejan el comportamiento del usuario en vez de props o estado, como se refleja en la guía de testing de React Native en Resumen de testing en React Native. Ese cambio es clave porque el code de React se reestructura constantemente. Los hooks se mueven. Los componentes se dividen. Se introduce Context. Un test ligado a la estructura interna se rompe durante un refactor sano. Un test ligado al comportamiento visible suele sobrevivir.
Qué debería proteger un unit test
Un buen unit test de React protege un pequeño contrato:
- Salida renderizada: ¿El usuario ve el texto, la etiqueta, el estado o el fallback correctos?
- Comportamiento de interacción: ¿Al hacer clic, escribir o alternar, la UI cambia correctamente?
- Manejo de límites: ¿El componente se comporta correctamente con entradas esperadas, datos faltantes o una ruta de error?
Una prueba débil protege lo equivocado:
- Detalles internos del componente: Forma del estado, métodos privados, props solo de implementación
- Mecánicas del framework: Si React actualizó un hook exactamente como esperabas a nivel interno
- Detalles del hijo: Markup de componentes anidados que no pretendes verificar aquí
Regla práctica: Si puedes refactorizar el componente sin cambiar lo que el usuario ve o hace, el test tampoco debería cambiar.
Los unit tests también forman parte de un sistema de testing más amplio. No intentan demostrar que toda la app funciona de extremo a extremo. Son la capa rápida que detecta regresiones antes de necesitar un test a nivel de navegador o una validación en un dispositivo. Por eso son la primera línea de defensa en cualquier stack sensato de testing automatizado para apps en producción.
Para equipos de React con despliegues frecuentes, la confianza viene de esta división del trabajo. Los unit tests detectan regresiones locales rápido. Los integration tests verifican las uniones. Los end-to-end tests confirman los flujos críticos. Si saltas la capa de unit testing, todo lo que sea más lento después tendrá que cargar con demasiado peso.
Configurando tu entorno moderno de testing en React
Un entorno de test frágil genera tests flaky antes de que escribas una sola assertion. Muchos desarrolladores culpan a Jest, jsdom o React cuando el problema real es una configuración inconsistente entre máquinas locales y CI. La solución es hacer que el entorno sea aburrido. Aquí, lo aburrido es bueno.

Empieza con un runner y un entorno predecibles
Para una app de React moderna, especialmente una creada con Vite, la configuración base debería incluir:
- Un test runner: Jest sigue siendo común, especialmente en codebases de React antiguos y en stacks de CI empresariales.
- Un entorno similar al navegador:
jsdomlets component tests render DOM output. - Utilidades de Testing Library:
@testing-library/reacty@testing-library/jest-dom - Un único punto de entrada de configuración: Un solo archivo para registrar matchers y mocks globales
El flujo de trabajo clave que refuerza la guía de pruebas de React es simple: renderizar el componente en un entorno respaldado por jsdom, consultar la UI con selectores como getByText o getByRole, disparar la interacción y afirmar el cambio en el DOM, como se describe en documentación de pruebas de React. Ese flujo solo sigue siendo fiable si cada máquina ejecuta el mismo entorno de pruebas.
Una configuración práctica de Jest suele verse así:
// jest.config.js
module.exports = {
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/src/setupTests.js'],
moduleNameMapper: {
'\\.(css|less|scss)$': 'identity-obj-proxy',
'^@/(.*)$': '<rootDir>/src/$1',
},
transform: {
'^.+\\.(js|jsx|ts|tsx)$': 'babel-jest',
},
};
Si tu equipo usa SWC en lugar de Babel, no hay problema. El punto no es el transformador. El punto es la consistencia. Elige un camino y estandarízalo en el repo. Si quieres una referencia complementaria para convenciones más amplias de pruebas en JavaScript, la de Capgo pruebas unitarias en JavaScript es un documento útil para la entrega al equipo.
Añade el archivo de configuración en el que dependerá tu suite
Un setupTests.js adecuado ahorra mucho ruido repetitivo:
import '@testing-library/jest-dom';
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation(query => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(),
removeListener: jest.fn(),
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});
Este archivo es donde resuelves las carencias del entorno una vez, en lugar de dentro de veinte archivos de prueba. Añade mocks para las APIs en las que depende tu UI, como matchMedia, ResizeObserver, o IntersectionObserver, si tu biblioteca de componentes los espera.
Sin esto, los desarrolladores parchearán los globales de forma ad hoc. Eso crea pruebas inconsistentes y fallos difíciles de rastrear. La ejecución local de una persona pasa porque añadió un mock manual en un archivo. CI falla porque la configuración no se compartió.
Alinea el comportamiento local con el de CI
El comando local debe ser lo más parecido posible al comando de CI. Si los devs usan watch mode con valores por defecto permisivos pero el CI usa una config más estricta, tendrás fallos inesperados tras el merge. Mantén los scripts explícitos:
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:ci": "jest --runInBand --coverage"
}
}
Una breve guía ayuda a que los nuevos miembros del equipo alcancen el mismo nivel rápidamente:
La decisión de configuración con mayor impacto es la disciplina con los valores por defecto. Usa aliases en la config. Pon los mocks de entorno en un solo archivo de setup. Usa jsdom para tests de UI y un entorno más ligero para utilidades puras cuando sea posible. Cuanto menos comportamiento personalizado necesite cada test, más fiable será tu sistema.
Escribir tests de componentes con sentido
Las empresas no tienen problemas para escribir tests. Tienen problemas para escribir tests que sigan siendo útiles seis meses después.
El patrón estándar para hacer unit testing de componentes de React sigue siendo el correcto: renderiza el componente, consulta la UI con selectores centrados en el usuario, dispara una interacción y valida el cambio resultante en el DOM, lo que evita que los tests dependan de detalles de implementación como el state o los props, tal como se describe en la guía de testing de React. El truco está en aplicar ese patrón con moderación.
Testea el acordeón tal como lo usaría un usuario
Toma un ejemplo básico Accordion component. Renderiza un botón con un título. El contenido del panel comienza oculto. Al hacer clic en el botón, se muestra el contenido y se actualiza el estado de accesibilidad.
Ese comportamiento es suficiente para varios tests útiles:
- El render inicial muestra el título pero no el contenido.
- Al hacer clic en el activador se muestra el contenido.
- Al hacer clic de nuevo, se colapsa.
- Los atributos de accesibilidad reflejan el estado visible.
Ese último punto se omite con demasiada frecuencia. Si tu componente usa aria-expanded, aria-controls, o una estructura basada en roles, verifícalos. No son detalles de implementación; son parte del contrato con el usuario.
Los mejores tests de componentes se leen como un reporte de bugs que nunca querrías recibir.
Elige las queries según la intención
React Testing Library ofrece varios estilos de query, pero no son intercambiables. Elegir la incorrecta hace que los tests sean ruidosos o engañosos.
| Tipo de consulta | Cuando se encuentra el elemento | Cuando no se encuentra el elemento | Ejemplo de caso de uso |
|---|---|---|---|
getBy |
Devuelve el elemento de inmediato | Lanza un error de inmediato | Afirmar que un botón o encabezado ya debe estar en pantalla |
queryBy |
Devuelve el elemento de inmediato | Devuelve null |
Afirmar que el contenido oculto no debe existir antes de la interacción |
findBy |
Se resuelve cuando aparece el elemento | Se rechaza tras esperar | Verifica que el contenido asíncrono aparezca tras un fetch o una actualización retrasada |
Un modelo mental sencillo ayuda:
- Usa
getBypara cosas que ya deben existir. - Usa
queryBypara cosas que aún no deben existir. - Usa
findBycuando la UI cambia más tarde.
Si una prueba empieza con findBy para todo, normalmente significa que el autor no tiene claro cuándo se actualiza el componente. Esa incertidumbre se vuelve inestabilidad después.
Un ejemplo práctico de acordeón
Aquí hay un componente representativo:
function Accordion({ title, children }) {
const [open, setOpen] = React.useState(false);
return (
<section>
<button
aria-expanded={open}
aria-controls="accordion-panel"
onClick={() => setOpen(prev => !prev)}
>
{title}
</button>
{open ? (
<div id="accordion-panel">
{children}
</div>
) : null}
</section>
);
}
Y esta es la forma de las pruebas que vale la pena conservar:
import { render, screen, fireEvent } from '@testing-library/react';
test('renders the accordion title and hides content initially', () => {
render(<Accordion title="Shipping details">Delivery takes 3 days</Accordion>);
expect(screen.getByRole('button', { name: /shipping details/i })).toBeInTheDocument();
expect(screen.queryByText(/delivery takes 3 days/i)).not.toBeInTheDocument();
});
test('reveals content when the trigger is clicked', () => {
render(<Accordion title="Shipping details">Delivery takes 3 days</Accordion>);
fireEvent.click(screen.getByRole('button', { name: /shipping details/i }));
expect(screen.getByText(/delivery takes 3 days/i)).toBeInTheDocument();
});
test('updates aria-expanded when opened', () => {
render(<Accordion title="Shipping details">Delivery takes 3 days</Accordion>);
const button = screen.getByRole('button', { name: /shipping details/i });
expect(button).toHaveAttribute('aria-expanded', 'false');
fireEvent.click(button);
expect(button).toHaveAttribute('aria-expanded', 'true');
});
Lo que falta es igual de importante. No hay ninguna aserción sobre el estado interno. No se verifica que setOpen se haya llamado. No hay una snapshot de todo el árbol renderizado. Esas pruebas añadirían mantenimiento, no confianza.
Algunos hábitos fortalecen las pruebas de componentes:
- Preferir consultas basadas en roles: Botones, encabezados, diálogos, alertas y campos de entrada deberían encontrarse normalmente por rol.
- Mantener cada prueba estrecha: Un comportamiento visible por usuario en cada test mantiene los fallos legibles.
- Nombrar las pruebas según los resultados: “actualiza aria-expanded al abrirse” es mucho más útil que “funciona correctamente”.
Si un componente es difícil de probar a través del DOM, a menudo revela un problema de diseño. Quizás oculta el estado en el lugar equivocado. Quizás le falta marcado semántico. Las buenas pruebas suelen empujar a los equipos hacia mejores componentes.
Testing de Custom Hooks y lógica de la aplicación
Las apps de React esconden mucha lógica importante fuera de los componentes. Las transiciones de estado viven en hooks. La validación y el formato están en funciones auxiliares. El procesamiento de datos suele ocurrir antes de que se renderice algo. Si solo pruebas componentes visibles, te perderás una gran parte del code que aún puede romper el comportamiento en producción.
Los Hooks requieren un entorno compatible con React
Un hook personalizado aún necesita React para ejecutarse correctamente, así que pruébalo con renderHook y envuelve las llamadas que cambian el estado en act().
Un pequeño useToggle hook es un buen ejemplo:
import { useState, useCallback } from 'react';
export function useToggle(initialValue = false) {
const [value, setValue] = useState(initialValue);
const toggle = useCallback(() => setValue(current => !current), []);
return { value, toggle };
}
Su prueba debe centrarse en el contrato público:
import { renderHook, act } from '@testing-library/react';
import { useToggle } from './useToggle';
test('returns the initial value', () => {
const { result } = renderHook(() => useToggle(true));
expect(result.current.value).toBe(true);
});
test('toggles the value', () => {
const { result } = renderHook(() => useToggle(false));
act(() => {
result.current.toggle();
});
expect(result.current.value).toBe(true);
});
Esa prueba es útil porque el hook en sí es la unidad. No estás probando los internos de React. Estás verificando el comportamiento externo del hook.
Para equipos de producto que construyen UI reutilizable o primitivas de funcionalidad, este patrón es muy importante. Los hooks a menudo se convierten en la interfaz compartida entre apps, design systems o herramientas internas. Si estás diseñando comportamiento reutilizable con intención comercial, los recursos sobre hooks para productos de makers pueden ayudar a ver los hooks como bloques de construcción productizados, no solo como detalles de implementación.
La lógica pura debe mantenerse pura en los tests
No todo necesita jsdom, React ni Testing Library. Si una función es pura, pruébala con Jest plano en un entorno Node.
Ejemplo:
export function formatDisplayName(firstName: string, lastName: string) {
return `${firstName.trim()} ${lastName.trim()}`.trim();
}
Esa prueba debe ser muy simple:
import { formatDisplayName } from './formatDisplayName';
test('joins and trims both names', () => {
expect(formatDisplayName(' Ada ', ' Lovelace ')).toBe('Ada Lovelace');
});
test('handles a missing last name', () => {
expect(formatDisplayName('Ada', '')).toBe('Ada');
});
La ventaja aquí es la velocidad y la claridad. Si una función no necesita un árbol renderizado, no le des uno. Las herramientas específicas de React añaden sobrecarga. Mantén las pruebas de la lógica de negocio pequeñas, rápidas y cercanas a la función que verifican.
Una separación práctica funciona bien:
- Hooks: Usa
renderHook,act(), y providers de envoltura cuando sea necesario. - Utilidades: Usa Jest plano y sin DOM.
- Lógica transversal con estado: Extráelo a helpers testeables cuando el test del componente empiece a hacer demasiado.
Los equipos suelen saturar los tests de componentes con aserciones de lógica que pertenecen a capas inferiores. Separar esa lógica te da dos beneficios: el test del componente queda más limpio y el test de la lógica es más rápido.
Dominando técnicas avanzadas: mocking y async
La mayoría de las suites de React poco fiables fallan en dos puntos: en los límites de las dependencias y por cuestiones de tiempo.
Por eso, el testing asíncrono y el mocking son la frontera entre una suite de tests de juguete y una en la que puedes confiar antes de un release. Un análisis atribuye el 46,5% de la inestabilidad de los tests a problemas de entorno o recursos, como el timing asíncrono en este análisis de unit testing en React. En apps de React, eso se traduce directamente en transiciones de estado, renderizado diferido, UI dependiente de la red y tests que adivinan en lugar de esperar de forma determinista.

Mockea el límite, no cada capa
La forma más rápida de escribir un test engañoso es mockear la mitad de tu árbol de componentes y luego verificar que tus propios mocks funcionaron.
Para un componente que obtiene datos de cuenta, mockea el cliente de red o el módulo API. No mockees el hook, el componente de la fila hija, el spinner de carga ni tres funciones de utilidad, a menos que el test realmente necesite aislamiento en esos puntos.
Usa este conjunto de reglas:
- Mockea servicios externos: Clientes HTTP, analytics, APIs exclusivas del navegador, puentes nativos
- Mockea APIs de plataforma inestables:
matchMedia, timers, interfaces de preload de Electron, plugins Capacitor cuando no estén disponibles en jsdom - Evita mockear tus propios internos por defecto: custom hooks, hijos simples, utilidades locales
Si un test pasa porque reemplazaste todas las partes difíciles con fakes, no te aporta mucha confianza para el release.
Para equipos que buscan ejemplos y patrones sobre APIs de runners, Capgo tutoriales de testing es una referencia práctica, especialmente al hacer el onboarding de desarrolladores que conocen React pero aún no dominan las mecánicas de testing.
Los tests async fallan si el timing es impreciso
Los fallos asíncronos suelen deberse a uno de estos tres errores:
- El test realiza el assert demasiado pronto.
- El test usa timers arbitrarios para esperar.
- El componente se actualiza varias veces, pero el test solo modela una transición.
Un test asíncrono estable suele tener esta estructura:
test('shows user details after data loads', async () => {
render(<UserProfile userId="42" />);
expect(screen.getByText(/loading/i)).toBeInTheDocument();
expect(await screen.findByText(/account owner/i)).toBeInTheDocument();
});
O, cuando necesitas esperar a una condición específica:
await waitFor(() => {
expect(screen.getByRole('alert')).toBeInTheDocument();
});
Usa findBy cuando la aparición de un elemento es el evento que te interesa. Usa waitFor cuando la condición es más amplia o el estado no se puede expresar con una sola query. Evita setTimeout en los tests, a menos que estés probando explícitamente el comportamiento de los timers y uses fake timers.
El ecosistema de testing de React también espera que respetes act() la semántica de las actualizaciones. Testing Library se encarga de gran parte de esto por ti, pero si controlas el estado manualmente o avanzas los timers, aún tienes que pensar en cuándo se aplican las actualizaciones.
Saber qué herramienta de mocking elegir
Distintas herramientas de mocking resuelven problemas distintos:
| Herramienta | Mejor uso | Error común |
|---|---|---|
jest.fn() |
Callbacks falsos independientes o funciones inyectadas | Usarlo para reemplazar un módulo entero cuando basta con un simple callback |
jest.spyOn() |
Observar o sobrescribir un método en un objeto o módulo real | Olvidar restaurar la implementación original |
jest.mock() |
Reemplaza una dependencia de módulo en el límite de importación | Mockear módulos grandes por defecto y perder comportamiento relevante |
Los ejemplos ayudan:
- Recurre a
jest.fn()cuando un componente recibe unonSubmitcontrolada. - Usa
jest.spyOn()cuando necesitas verificarconsole.error, un método de almacenamiento o una llamada API exportada. - Usa
jest.mock()cuando importar un módulo implicaría I/O, code nativo o comportamiento fuera del límite de la unidad.
Un área avanzada que muchas guías tratan insuficientemente es la prueba de rutas de error en React moderno. Los límites de error, los cambios de estado diferidos y las interfaces de respaldo asíncronas merecen pruebas de primera clase, no solo el ejemplo de clic del «camino feliz». Si un hijo lanza una excepción, afirma la interfaz de respaldo. Si una solicitud falla, afirma el estado de recuperación visible. Si un botón está deshabilitado durante la carga, afirma eso también. Esos son los errores que los usuarios recuerdan.
Mejorando la calidad y estrategia de tests
Muchos equipos aún persiguen la cobertura como si fuera lo mismo que la confianza. No lo es.
Puedes alcanzar un objetivo de cobertura y aun así pasar por alto las regresiones que importan. Una suite llena de aserciones superficiales, snapshots amplios y mocks internos crea la apariencia de seguridad mientras aumenta el costo de mantenimiento.

La cobertura es un mapa, no el objetivo
Los informes de cobertura son útiles cuando responden a una pregunta: ¿qué rutas críticas aún no tienen protección?
No son útiles cuando empujan a los desarrolladores a probar wrappers triviales, markup estático o archivos de una sola línea solo para mover un porcentaje. Trata la cobertura como una herramienta de descubrimiento. Si el estado de autenticación, las acciones de facturación, las feature flags o los update prompts no tienen pruebas, es una señal. Si un componente de icono presentacional no tiene pruebas, normalmente no lo es.
Una pregunta de revisión saludable es simple: ¿esta prueba reduce el riesgo de release?
- Sí: Verifica el comportamiento visible para el usuario en una ruta crítica.
- Quizás: Protege la lógica de negocio que es fácil de romper durante un refactor.
- No: Asegura detalles de implementación o duplica el valor de otra prueba.
Qué no testear con unit tests
Muchas guías de React aún no dedican suficiente tiempo a la omisión. Esa brecha importa porque el sobre-mockeo y las pruebas de detalles de implementación crean suites frágiles que pasan mientras la experiencia de usuario sigue rota, como se señala en la guía de BrowserStack sobre qué no probar en unit tests de React.
Omite o limita drásticamente estos patrones:
- Aserciones de estado interno: No pruebes
isOpendirectamente si puedes probar si el panel se abrió. - Comportamiento del framework: No pruebes que React llamó a un efecto. Prueba el resultado de lo que el efecto cambia.
- Internos de librerías de terceros: Prueba tu integración con un date picker o router, no la lógica de renderizado de la librería.
- Unidades sobre-mockeadas: Si has mockeado cada hijo y helper, es posible que ya no estés probando un comportamiento significativo.
Las pruebas malas son peores que las faltantes cuando bloquean refactors y aun así no capturan bugs de producción.
Una heurística útil es la propiedad de los límites. Prueba lo que tu code posee. No pruebes lo que React, el navegador o una biblioteca madura ya posee, a menos que tu capa de integración cambie el contrato.
Dónde ayudan los snapshots y dónde perjudican
Los snapshots no son inútiles. Simplemente es fácil usarlos mal.
Úsalos con moderación para componentes con salida estable y simple donde un diff estructural amplio sea significativo. Evítalos para componentes interactivos o altamente dinámicos, ya que se convierten en ruido. Los desarrolladores dejan de leerlos y empiezan a actualizarlos por reflejo.
Suelen existir alternativas mejores:
- Para el renderizado condicional, asérgate la presencia o ausencia de texto clave.
- Para cambios de estado visual, asérgate el role, label o atributo que importa.
- Para errores y fallbacks, asérgate el mensaje real o la región de alerta.
Si tu equipo necesita un proceso de calidad más amplio que las pruebas unitarias, un buen complemento es un flujo de trabajo de control de calidad de la app que trata las pruebas, las comprobaciones de release y la planificación de rollback como un solo sistema. Ese cambio de mentalidad es el que mejora la calidad de las pruebas más rápido. Deja de preguntarte cuántas pruebas tienes. Empieza a preguntarte qué fallos podrían llegar a los usuarios.
Integrando tests en un pipeline de CI/CD multiplataforma
Una suite de pruebas que solo corre en el portátil de un desarrollador es una sugerencia, no un control.
El suite se vuelve operativo cuando cada pull request ejecuta las mismas comprobaciones en un entorno limpio y bloquea los merges si esas comprobaciones fallan. Suena obvio, pero muchos equipos aún dejan huecos críticos. Las pruebas se ejecutan manualmente. Los informes de cobertura son opcionales. Los trabajos de empaquetado y release comienzan antes de que terminen los trabajos de pruebas. Así es como pequeñas regresiones de UI se cuelan en fallos de release más grandes.

Cada pull request debería activar el mismo gate
Para que el unit testing de React actúe como una red de seguridad, la CI necesita algunos elementos esenciales:
- Ejecutar en cada pull request
- Instalar dependencias desde el lockfile
- Usar el mismo comando de pruebas cada vez
- Fallar rápido ante fallos de pruebas
- Publica artefactos solo si los tests pasan
Este es el núcleo de las prácticas de continuous deployment para equipos de apps. Genera confianza antes del release, no después.
Un workflow sencillo de GitHub Actions basta para muchos equipos:
name: test
on:
pull_request:
push:
branches:
- main
jobs:
react-tests:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- name: Install dependencies
run: npm ci
- name: Run unit tests
run: npm run test:ci
No es nada sofisticado, y esa es la idea. Los pipelines más sólidos suelen ser los menos sorprendentes.
Por qué esto importa más para Capacitor y Electron
Las apps React multiplataforma conllevan más riesgo de release que las apps solo de navegador, porque la misma UI code suele distribuirse en contenedores distintos con supuestos de runtime diferentes.
Algunos ejemplos de dónde ayudan los pipelines:
- Apps Capacitor: code puede funcionar bien localmente, pero fallar si un plugin bridge, el estado offline o un caso extremo del ciclo de vida de la app cambian el comportamiento tras el empaquetado.
- Apps de Electron: Un componente de renderizado puede depender de APIs de preload, mensajería de window o estado exclusivo de escritorio que no existirá en pruebas de navegador plano a menos que se mockee deliberadamente.
- Trains de release compartidos: Un bundle defectuoso puede impactar múltiples objetivos si tu proceso de despliegue no controla estrictamente la publicación.
Por eso las pruebas unitarias deben ejecutarse antes de los jobs de empaquetado, y estos antes de los jobs de distribución. Cada etapa reduce el riesgo. Las pruebas unitarias detectan regresiones locales rápidamente. El empaquetado por plataforma verifica suposiciones del entorno. La aprobación manual o el despliegue gradual manejan la confianza final de la release.
Un workflow práctico de GitHub Actions
Un pipeline más maduro suele dividir responsabilidades:
- Job de pruebas: Pruebas unitarias y de hooks rápidas
- Job de build: Build de producción solo si las pruebas pasan
- Job de empaquetado: Sincronización de Capacitor, empaquetado de Electron o agrupación de artefactos
- Tarea de release: Publicar solo desde ramas o tags aprobados
Para equipos que publican live updates en Capacitor o apps de Electron, aquí es donde las herramientas de release importan. Una opción en ese flujo es Capgo, que publica bundles web firmados para apps de CapacitorJS y Electron con soporte de rollback y controles de rollout por canal. En la práctica, eso significa que tu tarea de pruebas de React puede actuar como la primera barrera dura antes de que cualquier bundle web se promueva a staging o producción.
La regla operativa es sencilla. No dejes que la infraestructura de release compense pruebas débiles. Usa la infraestructura de release después de que pruebas confiables ya hayan filtrado los cambios defectuosos.
Un sistema de pruebas confiable cambia el comportamiento del equipo. Los ingenieros hacen merge con menos dudas. Los revisores se enfocan en casos límite en lugar de re-ejecutar lo básico manualmente. Los managers de release dejan de tratar cada deploy como una apuesta. Ese es el resultado de hacer bien el unit testing en React.
Si tu equipo publica React a través de Capacitor o Electron, la seguridad del release depende de más que pruebas locales en verde. Capgo da a los equipos una forma controlada de publicar actualizaciones web firmadas, dirigir canales de rollout y revertir bundles defectuosos sin esperar la revisión de la store, lo cual encaja naturalmente detrás de un pipeline de CI que ya requiere pruebas unitarias aprobadas antes del despliegue.