Cómo vi.spyOn detecta errores reales

Resumen

Los spies son una herramienta de testing que envuelve tus funciones para observarlas sin reemplazarlas por completo. A diferencia de un mock tradicional, el spy mantiene el comportamiento real del código mientras registra cada llamada, cuenta cuántas veces se ejecutó y captura los argumentos usados. Si trabajas con custom hooks en React y Vitest, dominar esta técnica te permite verificar el flujo real de tu aplicación sin comprometer su código fuente.

Qué hace un spy y en qué se diferencia de un mock

Piensa en un spy como un detective silencioso: alguien que no conoces, pero que está observando cada movimiento de tu código.

Un spy hace cuatro cosas concretas:

  • Registra cada llamada que se hace a la función.
  • Cuenta cuántas veces se ejecutó.
  • Captura los argumentos que recibió.
  • Mantiene el comportamiento original o lo simula si tú lo necesitas.

¿Cuál es la diferencia entre un spy y un mock? Un mock reemplaza por completo el comportamiento de una función. Un spy lo envuelve, lo observa y verifica la función real, pero también puede simular retornos cuando lo necesitas.

Una regla clave: los spies solo aplican para archivos locales de tu proyecto. Para dependencias externas como React Router DOM, necesitas usar mocks tradicionales con vi.mock [04:30].

Cómo configurar un spy con vi.spyOn en Vitest

La configuración inicial del archivo useOrders.spy.test.tsx empieza importando todo el módulo con asterisco, no funciones específicas [02:10].

tsx import * as AuthContext from '../context/AuthContext'; import * as ReactRouter from 'react-router-dom'; import * as OrderService from '../services/getOrders';

Esta sintaxis es intencional: como el spy verifica toda la función dentro del módulo, necesitas pasarle el archivo completo asignado a una variable. Una buena práctica al ordenar imports es poner primero las dependencias externas y luego las internas del proyecto.

Cómo declarar las variables de tipo MockInstance

Las variables que sostendrán los spies se declaran inicialmente vacías y con tipo MockInstance, importado desde Vitest.

tsx let useSessionSpy: MockInstance; let getOrdersSpy: MockInstance; const mockNavigate = vi.fn();

¿Por qué MockInstance? Porque aunque el spy verifica el código real, técnicamente vive dentro del ambiente de test, no en el proyecto productivo.

Cómo usar beforeEach y afterEach con spies

Aquí entran dos funciones que ordenan el ciclo de vida de cada test:

  • beforeEach: se ejecuta automáticamente antes de cada caso de prueba. Es donde inicializas los spies.
  • afterEach: se ejecuta después de cada test. Es donde los restauras para que no contaminen otros casos.

tsx beforeEach(() => { useSessionSpy = vi.spyOn(AuthContext, 'useSession'); getOrdersSpy = vi.spyOn(OrderService, 'getOrders'); vi.mocked(ReactRouter.useNavigate).mockReturnValue(mockNavigate); vi.clearAllMocks(); });

afterEach(() => { vi.restoreAllMocks(); });

Resetear los spies en cada ciclo evita que las llamadas previas se acumulen y rompan tus aserciones.

Cómo escribir un caso de prueba para errores con spy

El caso de prueba simula que la API falla y verifica que el custom hook useOrders reaccione correctamente. Para forzar el rechazo de la promesa se usa mockRejectedValue [09:15].

tsx it('debería mostrar un error', async () => { useSessionSpy.mockReturnValue({ userId: 1 }); getOrdersSpy.mockRejectedValue(new ApiError());

const { result, waitForNextUpdate } = renderHook(() => useOrders());

expect(result.current.loading).toBe(true); expect(result.current.error).toBeNull();

await waitForNextUpdate();

expect(result.current.loading).toBe(false); expect(result.current.error).toBe('Failed to fetch orders. Por favor, intenta nuevamente'); expect(getOrdersSpy).toHaveBeenCalledTimes(1); });

¿Qué hace toHaveBeenCalledTimes? Verifica el número exacto de veces que se invocó una función espiada. Si esperas una llamada y se ejecutan dos, el test falla.

Un detalle importante: el mensaje de error que valida el test no es el de la ApiError original. Si revisas el hook, el texto que se asigna al estado de error es siempre "Failed to fetch orders. Por favor, intenta nuevamente". Por eso siempre debes leer el código antes de escribir la aserción.

Por qué un spy detecta errores que un mock no vería

Aquí viene lo interesante. Si introduces un error de sintaxis dentro de la función real getOrders, por ejemplo escribiendo ASB suelto en el archivo, los tests que usan mocks completos seguirán pasando. Los mocks reemplazan por completo el actor principal, así que ni se enteran del problema [13:40].

En cambio, el test con spy falla inmediatamente. ¿Por qué? Porque el spy sigue conectado a la función real: la verifica, la observa, aunque simule su retorno. Si la función original tiene un error de sintaxis o ya no existe, tu test te lo dice.

Esa es la ventaja real: los spies son ojos en tu código que interfieren sin molestar. Te avisan cuando la implementación real se rompe, incluso mientras estás simulando comportamientos para tus pruebas.

Si quieres verificar que el flujo de tu aplicación funciona sin comprometerte a editar tu código fuente, spy es la solución. ¿Ya probaste reemplazar tus mocks locales por spies en tus tests actuales? Cuéntame qué descubriste.