Cuando escribes pruebas unitarias para componentes con múltiples escenarios, copiar y pegar el mismo flujo una y otra vez se convierte en un problema real. Existe una técnica que resuelve esto de forma directa: table driven testing, un patrón que centraliza todos los casos de prueba en un solo lugar y los ejecuta de manera iterada.
¿Qué es table driven testing y por qué usarlo?
Table driven testing consiste en agrupar varios casos de prueba que comparten un flujo similar, pero cuyos resultados esperados son distintos [0:06]. En lugar de escribir un test individual para cada escenario, defines un arreglo con las entradas y salidas esperadas, y un único test se encarga de recorrerlos todos.
Esta técnica ofrece cuatro beneficios claros [0:19]:
- Mantenibilidad: editar un array es mucho más sencillo que modificar cada caso de prueba por separado.
- Legibilidad: al tener todo centralizado, es fácil ver las entradas y las salidas esperadas de cada escenario.
- Eficiencia: un solo test ejecuta todos los casos de prueba del componente.
- Cobertura: al tener todos los casos juntos, es más difícil olvidar escenarios pequeños pero importantes.
¿Cómo implementar table driven testing con un componente Calculator?
El ejemplo parte de un componente llamado Calculator que recibe dos números (A y B) y una operación (sum, subtract, multiply o divide) [1:07]. Internamente, usa un switch case para realizar el cálculo correspondiente y renderiza el resultado en pantalla.
Este componente es candidato perfecto porque el flujo siempre es el mismo: recibir dos números, aplicar la operación y mostrar el resultado.
¿Cómo se estructura el arreglo de casos de prueba?
Dentro del archivo calculator.test.tsx, después de importar describe, it y expect desde Vitest, se crea un array llamado useCasesTest [1:52]:
tsx
const useCasesTest = [
{ a: 1, b: 2, operation: 'sum', expected: 3 },
{ a: 3, b: 2, operation: 'multiply', expected: 6 },
];
Cada objeto del arreglo contiene las entradas (a, b, operation) y la salida esperada (expected). Agregar un nuevo escenario es tan simple como añadir otro objeto al array.
¿Cómo se usa el método each para iterar los casos?
La variación clave está en el método .each del caso de prueba [3:07]. Funciona como un forEach en programación: recibe el arreglo y ejecuta el test para cada elemento.
tsx
describe('Calculator', () => {
it.each(useCasesTest)(
'debería retornar $expected cuando $a y $b son $operation',
({ a, b, operation, expected }) => {
render(<Calculator a={a} b={b} operation={operation} />);
const resultado = screen.getByText(result: ${expected});
expect(resultado).toBeInTheDocument();
}
);
});
El signo $ dentro del nombre del test se reemplaza dinámicamente con el valor de cada key del objeto [3:17]. Esto hace que al ejecutar las pruebas, cada caso muestre un mensaje descriptivo.
Un detalle importante: los parámetros a, b, operation y expected deben destructurarse como objeto en el callback [5:09]. Si se pasan como parámetros individuales sin destructurar, el componente no recibe las props correctamente y devuelve InvalidOperation, que es el caso default del switch.
¿Cómo validar que los tests funcionan correctamente?
Una forma rápida de verificar es modificar un expected a un valor incorrecto. Por ejemplo, si cambias el resultado esperado de 3 * 2 de 6 a 9, el test fallará indicando que el resultado fue 6 pero se esperaba 9 [5:30]. Esto confirma que la aserción está funcionando.
El componente Calculator también tiene un caso especial: la división entre cero arroja un error [5:55]. Este es exactamente el tipo de escenario que table driven testing facilita cubrir, ya que basta con agregar una entrada más al arreglo.
Si quieres practicar, completa el array con los casos de multiplicación, división y ese caso borde de división por cero. Verás cómo un solo test puede cubrir toda la lógica del componente sin repetir código.