Introducción a TDD (Test-Driven Development)
¿Qué es TDD?
- TDD es una técnica para desarrollar software guiada por pruebas. Aunque incluye pruebas, no es solo una técnica de testing, sino un enfoque completo para escribir código de calidad.
Problemas con no usar TDD
- Falta de retroalimentación del código: Los tests hechos después pueden no reflejar correctamente los posibles errores o “corner cases”.
- Percepción de pérdida de tiempo: Podrías evitar hacer pruebas creyendo que no son necesarias, dejando posibles errores sin cubrir.
- Foco en el “happy path”: Tests hechos después tienden a probar solo los casos positivos, olvidando escenarios menos comunes.
Diferencia entre TDD y QA
- TDD ocurre antes y durante el desarrollo; QA se realiza en fases posteriores, validando que el sistema completo funcione correctamente.
- Usar TDD puede facilitar y acelerar el proceso de QA al tener una base de pruebas sólida.
💡 Reglas del TDD
- No escribas código de producción sin antes escribir un test que falle.
- Escribe solo un test a la vez (el necesario para que falle).
- Escribe el código mínimo necesario para hacer que pase el test (sin añadir lógica extra).
El ciclo del TDD
- El flujo principal de TDD sigue tres etapas:
- Rojo : Escribe un test que falle (aún no hay código que pase el test).
- Verde : Escribe el código mínimo necesario para que el test pase.
- Refactor : Mejora y optimiza el código existente asegurándote de que los tests sigan pasando.

✅ Beneficios del TDD
- Refactorización confiable: Puedes mejorar el código sin miedo a romper funcionalidades.
- Mejor diseño: Obliga a pensar en la estructura desde el inicio.
- Mayor cobertura de pruebas: Asegura que más partes del código están cubiertas.
- Depuración más rápida: Localiza errores rápidamente.
- Documentación implícita: Los tests describen el comportamiento esperado del código.
Configuración Inicial
-
Instalar dependencias:
En el proyecto en el que estés trabajando, ejecutar lo siguiente:
- Ejecutar
npm install -D vitestpara añadir el framework Vitest. - Instalar standard:
npm install standard -Dpara configurar buenas prácticas con reglas de linting.
- Ejecutar
-
Configurar
package.json:-
Añadir los siguientes scripts:
scripts": {"test": "vitest", /*Por defecto lo que hace es un watch, va a estar constantemente mirando los cambios*/"coverage": "vitest run --coverage"}
-
-
Crear carpeta para pruebas: (para practicar)
- Crear una carpeta
testsdonde estarán los archivos de prueba, que se llamarán<nombre_archivo>.test.ts.
- Crear una carpeta
Conceptos Básicos
1. it o test
- Define un caso de prueba específico.
- “Esto debería…” (indica lo que esperas que el código haga).
Ejemplo:
it("debería sumar dos números correctamente", () => { const resultado = suma(2, 3); expect(resultado).toBe(5);});2. expect
- Sirve para declarar las expectativas sobre el resultado de una prueba.
- “Espero que…” (Compara el valor que devuelve una función o componente con un valor esperado). **Formato: **
expect(valorReal).matcher(valorEsperado);Ejemplo:
expect(2 + 2).toBe(4); // Verifica que 2+2 sea igual a 4Métodos usados con expect para comparar valores:
toBe(valor): Verifica igualdad estricta.expect(2 + 2).toBe(4);expect(true).toBe(true);toEqual(objeto): Verifica equivalencia (útil para objetos y arrays).expect({ a: 1 }).toEqual({ a: 1 });toContain(valor): Verifica si un array o string contiene un elemento.expect([1, 2, 3]).toContain(2);toBeTruthy()ytoBeFalsy(): Verifican si un valor es “true” o “false” respectivamente.expect(true).toBeTruthy();expect(false).toBeFalsy();
3. describe
-
Agrupa casos de prueba relacionados.
-
Ejemplo:
describe("Funciones matemáticas", () => {it("debería sumar números", () => {expect(suma(2, 3)).toBe(5);});it("debería restar números", () => {expect(resta(5, 3)).toBe(2);});});
Testing Types
¿Qué son las pruebas de tipos en Vitest?
Vitest permite probar tipos en tu código TypeScript usando métodos como expectTypeOf y assertType. Estas pruebas verifican que los tipos definidos en tu proyecto son correctos y consistentes.
expectTypeOf: Compara un tipo real con el esperado.
assertType: Valida que un valor coincida con un tipo específico.
Archivos para pruebas de tipos
- Los archivos
.test-d.tsse consideran automáticamente pruebas de tipos. - Puedes personalizar qué archivos se incluyen usando la opción
typecheck.include.
❔ Cómo funciona
- Vitest utiliza herramientas como
tscovue-tscpara analizar los tipos en tu código. - Los errores de tipo en las pruebas se consideran fallos y se reportan en el resultado de las pruebas.
Limitaciones
- Los archivos de prueba de tipos no se ejecutan; se analizan de manera estática.
- Métodos como
test.eachotest.forno generan nombres dinámicos para las pruebas de tipos.
Compatibilidad con CLI
Vitest admite verificaciones de tipos junto con indicadores como --allowOnly y -t.
Ejemplo de Pruebas de Tipos
export function add(a: number, b: number): number { return a + b;}import { expectTypeOf, assertType } from 'vitest';import { add } from './calculator';
it('should return a number from add function', () => { const result = add(2, 3);
// Verificamos que el tipo de `result` sea 'number' expectTypeOf(result).toEqualTypeOf<number>();});
test('should ensure the type of result is number using assertType', () => { const result: number = add(4, 5);
// Aseguramos que el tipo de `result` es 'number' assertType<number>(result);});❌ Errores en las pruebas de tipos
- Los errores generados por
expectTypeOfintentan ser lo más claros posible. - En caso de fallos, muestran una comparación entre el tipo esperado y el tipo real:
Expected: string, Actual: number
- Si usas nombres concretos (por ejemplo,
{ a: '' }), los mensajes de error pueden ser menos detallados que al usar un tipo genérico (por ejemplo,<{ a: string }>).
Ejemplo de error útil:
expectTypeOf({ a: 1 }).toEqualTypeOf<{ a: string }>();✅ Buenas prácticas
- Preferiblemente tipos genéricos en
expectTypeOfpara obtener mensajes de error más útiles. - Usa
@ts-expect-errorpara probar errores intencionales, pero verifica que no tengan errores tipográficos. - Compara tipos concretos con
typeofsi necesitas comparar valores complejos:ts expectTypeOf(one).toEqualTypeOf<typeof two>()
Vitest puede combinar la validación de tipos con pruebas regulares. Incluye los archivos de prueba de tipos en test.include para evitar errores de configuración o tipográficos.
Este enfoque asegura que tanto el comportamiento como los tipos de tu proyecto estén bien cubiertos.
Test Filtering
Vitest proporciona varias herramientas útiles para filtrar y controlar la ejecución de pruebas, lo que te permite enfocarte solo en las pruebas que necesitas ejecutar, gestionar tiempos de espera y organizar las pruebas que aún no se han implementado. A continuación se detallan los métodos principales:
1. Filtrado de Pruebas
Puedes filtrar las pruebas usando el nombre de archivo o el nombre de la prueba para ejecutar solo aquellas que te interesen. Esto es útil cuando tienes una gran cantidad de pruebas y solo quieres ejecutar un subconjunto.
-
Filtrado por Nombre de Archivo: Para ejecutar pruebas solo de archivos cuyo nombre contiene un patrón específico, usa la CLI de Vitest:
Terminal window vitest basic
Esto solo ejecutará los archivos de prueba que contengan basic en su nombre, como basic.test.ts o basic/foo.test.ts.
- Filtrado por Nombre de Prueba:
Para filtrar pruebas específicas dentro de los archivos, puedes usar la opción
to-testNamePattern <pattern>:Esto ejecutará solo las pruebas que coincidan con el patrón “name” dentro de los archivos.Terminal window vitest -t "name"
2. Especificación de un Tiempo de Espera
Puedes especificar un tiempo de espera personalizado para las pruebas y los hooks, como beforeAll, beforeEach, etc. El valor predeterminado es de 5 segundos. Si necesitas más tiempo, puedes ajustarlo en milisegundos como tercer argumento en la prueba o gancho:
- Tiempo de Espera en test:
import { test } from "vitest";test("name", async () => {/* ... */}, 1000); // 1 segundo
- Tiempo de Espera en Hooks:
import { beforeAll } from "vitest";beforeAll(async () => {/* ... */}, 1000); // 1 segundo
3. Saltarse Pruebas y Suites
Si quieres evitar que ciertas pruebas o suites se ejecuten, puedes usar .skip. Esto es útil para omitir pruebas que no quieras ejecutar temporalmente.
- Saltarse una Suite:
describe.skip("skipped suite", () => {test("test", () => {// Suite omitida});});
- Saltarse una Prueba:
describe('suite', () => {test.skip('skipped test', () => {// Prueba omitida})}
4. Ejecutar Solo Ciertas Pruebas o Suites
Si deseas ejecutar únicamente pruebas o suites específicas, puedes usar .only. Esto asegura que solo se ejecuten las pruebas marcadas con .only.
- Ejecutar Solo una Suite:
describe.only("suite", () => {test("test", () => {// Solo se ejecuta esta suite});});
- Ejecutar Solo una Prueba:
describe("suite", () => {test.only("test", () => {// Solo se ejecuta esta prueba});});
5. Pruebas y Suites No Implementadas
Si tienes pruebas o suites que aún no están implementadas, puedes usar .todo para marcarlas como pendientes. Esto también se reflejará en los reportes de las pruebas.
- Suite No Implementada:
describe.todo("unimplemented suite");
- Prueba No Implementada:
describe("suite", () => {test.todo("unimplemented test");});
✅ Beneficios de Filtrar y Controlar las Pruebas
- Filtrado: Permite ejecutar solo las pruebas relevantes, ahorrando tiempo de ejecución.
- Tiempos de Espera Personalizados: Evita que las pruebas se bloqueen por tiempos de espera predeterminados demasiado cortos.
- Concurrencia Controlada: Puedes omitir o ejecutar solo ciertas pruebas para reducir la carga durante el desarrollo.
- Marcado de Tareas Pendientes: Facilita la gestión de pruebas aún no implementadas.
Reporters
Vitest permite usar diferentes Reporters para mostrar los resultados de las pruebas.
Los reporteros pueden ser especificados a través de la línea de comandos o en el archivo vitest.config.ts. Si no se elige un reportero, Vitest utiliza el predeterminado.
Los reporters disponibles incluyen opciones como:
Uso de reporters a través de la línea de comandos:
npx vitest --reporter=verboseUtilizando reporters a través de vitest.config.ts.:
import { defineConfig } from "vitest/config";export default defineConfig({ test: { reporters: ["verbose"], },});Algunos reporters se pueden personalizar pasándoles opciones adicionales. Las opciones específicas de reportero se describen en las secciones siguientes.
export default defineConfig({ test: { reporters: ["default", ["junit", { suiteName: "UI tests" }]], },});Salida de Reportes en Vitest
Por defecto, Vitest muestra los resultados de tus pruebas en la terminal. Si quieres guardar esos resultados en un archivo, puedes hacerlo utilizando reporteros como json, html o junit y especificando un archivo de salida.
Usar la CLI (línea de comandos)
Si quieres que los resultados se guarden en un archivo, usa este comando:
npx vitest --reporter=json --outputFile=./test-output.jsonEsto guardará los resultados en un archivo llamado test-output.json.
Usar Múltiples Reporteros
Puedes usar más de un reportero al mismo tiempo. Por ejemplo, si quieres que los resultados se vean en la terminal y también se guarden en un archivo JSON, puedes hacerlo así:
npx vitest --reporter=json --reporter=defaultEn el archivo de configuración (vitest.config.ts), sería algo así:
export default defineConfig({ test: { reporters: ["json", "default"], outputFile: "./test-output.json", },});Esto muestra los resultados en la terminal y también los guarda en un archivo JSON.
Archivos de Salida Diferentes
Si usas varios reporteros, puedes guardar los resultados en archivos diferentes. Por ejemplo:
export default defineConfig({ test: { reporters: ["junit", "json", "verbose"], outputFile: { junit: "./junit-report.xml", json: "./json-report.json", }, },});Este código guarda los resultados en dos archivos diferentes: uno en formato JSON (json-report.json) y otro en formato XML (junit-report.xml). Además, mostrará un reporte detallado en la terminal.
Coverage
En Vitest, coverage (cobertura) se refiere a la cobertura de pruebas, que mide qué partes del código han sido ejecutadas (cubiertas) por el conjunto de pruebas que has escrito. Es una métrica utilizada para determinar qué tan completo es tu conjunto de pruebas en relación con el código de tu proyecto.
¿Por qué es útil?
- Identificar código no probado: Ayuda a encontrar partes de tu código que no están cubiertas por las pruebas.
- Mejorar la calidad del software: Tener una cobertura alta reduce la probabilidad de errores no detectados.
- Priorizar pruebas: Si hay áreas críticas con baja cobertura, puedes priorizar la escritura de pruebas para esas partes.
Vitest admite cobertura de código usando dos herramientas: v8 e Istanbul. De forma predeterminada, se usa v8, pero puedes cambiarlo a Istanbul en el archivo de configuración vitest.config.ts.
Configuración básica de cobertura
-
Seleccionar proveedor de cobertura: Puedes configurar el proveedor de cobertura en
test.coverage.provider:vitest.config.ts import { defineConfig } from "vitest/config";export default defineConfig({test: {coverage: {provider: "istanbul", // o 'v8'},},}); -
Instalar paquetes: Vitest instalará automáticamente el paquete necesario al iniciarlo, o puedes instalarlo manualmente:
Terminal window npm i -D @vitest/coverage-v8 # Para v8npm i -D @vitest/coverage-istanbul # Para Istanbul
Configuración avanzada
-
Definir archivos a incluir: Se recomienda usar
coverage.includepara reducir los archivos seleccionados por cobertura. -
Activar cobertura en CLI: Para ejecutar pruebas con cobertura habilitada, puedes usar:
Terminal window vitest run --coverage -
Configurar reportes: Puedes definir los reportes de cobertura en el archivo de configuración:
vitest.config.ts export default defineConfig({test: {coverage: {reporter: ["text", "json", "html"],},},}); -
Usar reporteros personalizados: Vitest permite usar reporteros personalizados, tanto desde un paquete de NPM o una ruta local:
vitest.config.ts export default defineConfig({test: {coverage: {reporter: [["@vitest/custom-coverage-reporter", { someOption: true }],"/absolute/path/to/custom-reporter.cjs",],},},});
Cobertura personalizada
-
Proveedor de cobertura personalizado: Puedes crear un proveedor de cobertura personalizado especificando un módulo en
test.coverage.provider:vitest.config.ts export default defineConfig({test: {coverage: {provider: 'custom',customProviderModule: 'my-custom-coverage-provider',},},})``` -
Cambiar ubicación de los reportes: Puedes cambiar la carpeta donde se almacenan los reportes de cobertura:
vitest.config.ts export default defineConfig({test: {coverage: {reportsDirectory: './tests/unit/coverage',},},})```
Ignorar código en cobertura
- Istanbul y v8 tienen formas de ignorar líneas de código para que no se incluyan en el reporte de cobertura:
- Istanbul: Utiliza
/* istanbul ignore if -- @preserve */. - v8: Usa
/* v8 ignore next 3 */para ignorar líneas
- Istanbul: Utiliza
Snapshot
Las pruebas instantáneas (Snapshot) son una herramienta útil para garantizar que la salida de tus funciones no cambie inesperadamente. Vitest toma una instantánea de un valor durante la prueba y la compara con un archivo de instantánea de referencia almacenado junto con las pruebas. Si los resultados no coinciden, la prueba falla y se debe revisar si se necesita actualizar la instantánea.
Los snapshots en Vitest son una forma simple y efectiva de verificar que el resultado de una función, componente, o cualquier estructura de datos no cambie inesperadamente. Son como una “foto” del estado esperado en un momento específico y se comparan en futuras ejecuciones para detectar diferencias.
¿Cómo funcionan los snapshots?
-
Crean un archivo con el “resultado esperado”:
Cuando corres una prueba por primera vez, Vitest guarda el resultado esperado en un archivo
.snap. -
Comparan los resultados futuros con el snapshot:
Cada vez que corres las pruebas, Vitest compara el resultado actual con lo guardado en el snapshot.
- Si coinciden: ✅ La prueba pasa.
- Si no coinciden: ❌ La prueba falla (lo que puede indicar un cambio intencional o un error inesperado).
Ejemplo simple
- Función a probar:
export function getUser() { return { id: 1, name: "Rocio", age: 25 };}- Prueba con snapshot:
import { it, expect } from "vitest";import { getUser } from "./user";
it("getUser returns correct user data", () => { expect(getUser()).toMatchSnapshot();});-
Primera ejecución:
Vitest guarda este resultado en un archivo snapshot:
// user.test.ts.snapexports[`getUser returns correct user data`] = ` Object { "age": 25, "id": 1, "name": "Rocio", }`;-
Futuras ejecuciones:
Si el resultado de
getUser()cambia (por ejemplo, el nombre ahora es “David”), la prueba fallará.
Si cambiaste algo a propósito y quieres actualizar el snapshot:
-
Corre las pruebas con
-updateou:Terminal window npx vitest -u -
Vitest actualizará el archivo
.snapcon el nuevo resultado.
Cómo usar instantáneas
Para capturar una instantánea de un valor, puedes usar la API toMatchSnapshot() dentro de expect():
import { expect, it } from "vitest";
it("toUpperCase", () => { const result = toUpperCase("foobar"); expect(result).toMatchSnapshot();});Cuando ejecutas esta prueba por primera vez, Vitest crea un archivo de instantánea con un contenido como este:
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.htmlexports["toUpperCase 1"] = '"FOOBAR"';Este archivo de instantánea debe confirmarse junto con los cambios de código, y cualquier cambio en el código que afecte la salida de la función generará una diferencia en la instantánea que debe ser revisada.
Actualizar instantáneas
Si la salida cambia y se desea aceptar el nuevo valor como correcto, se puede actualizar la instantánea de varias maneras:
- Usar la tecla
uen la terminal mientras se ejecutan las pruebas en modo de observación. - Usar el flag
-updateouen la CLI para actualizar las instantáneas.
vitest -u-
¿Qué hacer si el cambio es intencional?
Si cambiaste algo a propósito y quieres actualizar el snapshot:
-
Corre las pruebas con
-updateou:Terminal window npx vitest -u -
Vitest actualizará el archivo
.snapcon el nuevo resultado.
Instantáneas en línea
En lugar de generar un archivo de instantánea, puedes almacenar la instantánea directamente en el archivo de prueba usando toMatchInlineSnapshot():
import { expect, it } from "vitest";
it("toUpperCase", () => { const result = toUpperCase("foobar"); expect(result).toMatchInlineSnapshot('"FOOBAR"');});Esto evita tener que saltar entre diferentes archivos y permite ver el resultado esperado directamente en el archivo de prueba.
Instantáneas de archivos
Puedes almacenar las instantáneas en archivos y realizar comparaciones más legibles usando toMatchFileSnapshot():
import { expect, it } from "vitest";
test("render basic", async () => { const result = renderHTML(h("div", { class: "foo" })); await expect(result).toMatchFileSnapshot("./test/basic.output.html");});Instantáneas de imágenes
Para tomar instantáneas de imágenes, puedes usar el paquete jest-image-snapshot:
npm i -D jest-image-snapshottest("image snapshot", () => { expect(readFileSync("./test/stubs/image.png")).toMatchImageSnapshot();});Serializadores personalizados
Puedes personalizar la forma en que se serializan las instantáneas, por ejemplo, añadiendo tu propia lógica para transformar el valor antes de almacenarlo:
expect.addSnapshotSerializer({ serialize(val, config, indentation, depth, refs, printer) { return `Pretty foo: ${printer(val.foo, config, indentation, depth, refs)}` }, test(val) { return val && Object.prototype.hasOwnProperty.call(val, 'foo') },}Diferencias con Jest
-
Encabezado de la instantánea: Vitest usa un encabezado diferente para las instantáneas en comparación con Jest:
//Jest Snapshot v1, https://goo.gl/fbAQLP+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -
printBasicPrototype: En Vitest, este valor predeterminado esfalse, mientras que en Jest estrue. Esto afecta cómo se imprimen los objetos en las instantáneas. -
Separadores personalizados: Vitest usa
>en lugar de:para los mensajes personalizados en las instantáneas.
☝️🤓 Jest es un framework de pruebas para JavaScript, desarrollado por Facebook.
Permite escribir y ejecutar pruebas automatizadas para asegurarse de que el código funciona correctamente. Es fácil de configurar, rápido y tiene características como:
- Pruebas unitarias: Verificar funciones o componentes individualmente.
- Snapshots: Comparar el resultado de una prueba con un valor guardado.
- Mocks: Simular partes del código para aislar las pruebas.
Se usa comúnmente con React, pero funciona con cualquier código JavaScript.
Mocking
Al escribir pruebas, es solo cuestión de tiempo antes de que necesites crear una versión “falsa” de un servicio interno (o externo). Esto se conoce comúnmente como mocking . Vitest proporciona funciones de utilidad para ayudarte a través de su asistente vi .
Puedes import { vi } from 'vitest' acceder a él de forma global (cuando la configuración global está habilitada ).
⚠️ Recuerda siempre borrar o restaurar los mocks antes o después de cada
ejecución de prueba para deshacer los cambios de estado de los simulacros
entre ejecuciones, con funciones como vi.restoreAllMocks() o
vi.mockReset()
Date
A veces es necesario controlar la fecha para garantizar la coherencia durante las pruebas. Vitest utiliza **@sinonjs/fake-timers,** un paquete para manipular temporizadores, así como la fecha del sistema.
- Usa
vi.useFakeTimers()yvi.setSystemTime()para simular fechas y controlar temporizadores.
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";const businessHours = [9, 17];
function purchase() { const currentHour = new Date().getHours(); const [open, close] = businessHours; if (currentHour > open && currentHour < close) { return { message: "Success" }; } return { message: "Error" };}
describe("flujo de compras", () => { beforeEach(() => { // indicar a vitest que estamos usando tiempo simulado vi.useFakeTimers(); }); afterEach(() => { // restaurar la fecha después de cada ejecución de prueba vi.useRealTimers(); }); it("permite compras dentro del horario laboral", () => { // establecer la hora dentro del horario laboral const date = new Date(2000, 1, 1, 13); vi.setSystemTime(date); // acceder a Date.now() dará como resultado la fecha establecida arriba expect(purchase()).toEqual({ message: "Success" }); }); it("no permite compras fuera del horario laboral", () => { // establecer la hora fuera del horario laboral const date = new Date(2000, 1, 1, 19); vi.setSystemTime(date); // acceder a Date.now() dará como resultado la fecha establecida arriba expect(purchase()).toEqual({ message: "Error" }); });});Globales
- Usa
vi.stubGlobal()para simular variables o funciones globales no presentes (ejemplo:IntersectionObserver).
import { vi } from "vitest";const IntersectionObserverMock = vi.fn(() => ({ disconnect: vi.fn(), observe: vi.fn(), takeRecords: vi.fn(), unobserve: vi.fn(),}));
vi.stubGlobal("IntersectionObserver", IntersectionObserverMock);✅ Buenas prácticas
- Limpia mocks con
afterEachusandovi.restoreAllMocks()o similares. - Usa mocks para funciones críticas o dependencias externas.
Funciones
Las funciones simuladas se clasifican en dos categorías principales: espionaje (spying) y simulación (mocking).
- Spying con
vi.spyOn: Permite observar si una función fue llamada y con qué argumentos, sin modificar su comportamiento. Es útil cuando solo necesitas monitorear el uso de una función existente. - Mocking con
vi.fn: Permite crear versiones falsas o simuladas de funciones, permitiendo alterar su implementación. Esto es ideal en pruebas donde se necesita reemplazar el comportamiento original de una función para controlar las condiciones del test.
Diferencias clave:
vi.spyOn: Observa funciones existentes sin modificarlas.vi.fn: Crea funciones simuladas que son ejecutables directamente.
Compatibilidad:
Vitest utiliza Tinyspy como base para estas funcionalidades, pero lo adapta para garantizar la compatibilidad con Jest. Aunque vi.fn y vi.spyOn comparten métodos similares, solo las funciones simuladas creadas con vi.fn son ejecutables.
import { afterEach, describe, expect, it, vi } from "vitest";
const messages = { items: [ { message: "Simple test message", from: "Testman" }, // ... ], getLatest, //también puede ser un getter o setter si es compatible};
function getLatest(index = messages.items.length - 1) { return messages.items[index];}
describe("reading messages", () => { afterEach(() => { vi.restoreAllMocks(); });
it("should get the latest message with a spy", () => { const spy = vi.spyOn(messages, "getLatest"); expect(spy.getMockName()).toEqual("getLatest");
expect(messages.getLatest()).toEqual( messages.items[messages.items.length - 1] );
expect(spy).toHaveBeenCalledTimes(1);
spy.mockImplementationOnce(() => "access-restricted"); expect(messages.getLatest()).toEqual("access-restricted");
expect(spy).toHaveBeenCalledTimes(2); });
it("should get with a mock", () => { const mock = vi.fn().mockImplementation(getLatest);
expect(mock()).toEqual(messages.items[messages.items.length - 1]); expect(mock).toHaveBeenCalledTimes(1);
mock.mockImplementationOnce(() => "access-restricted"); expect(mock()).toEqual("access-restricted");
expect(mock).toHaveBeenCalledTimes(2);
expect(mock()).toEqual(messages.items[messages.items.length - 1]); expect(mock).toHaveBeenCalledTimes(3); });});Modules
Los módulos mock observan bibliotecas de terceros que son invocadas en otro código, permitiendo probar los argumentos, la salida o incluso redefinir su implementación.
1. Automocking (Mocking automático)
Principio: Si tu código importa un módulo que está siendo mockeado, Vitest lo mockeará automáticamente si no hay un archivo o fábrica __mocks__.
Ejemplo:
export function foo() { return "foo";}
export function bar() { return `${foo()} bar`;}import { vi } from "vitest";import * as mod from "./foobar.js";
// Mocking automático de todo el módulovi.mock("./foobar.js");
describe("Testing foo and bar", () => { it("should mock foo and bar", () => { expect(mod.foo()).toBe(undefined); // La función foo es mockeada expect(mod.bar()).toBe("undefined bar"); // Dependiendo de la implementación del mock });});2. Virtual Modules (Módulos Virtuales)
Principio: Vitest admite el mockeo de módulos virtuales de Vite, diferente a cómo se tratan en Jest.
Ejemplo:
export default { test: { alias: { "$app/forms": resolve("./mocks/forms.js"), }, },};3. Mocking Pitfalls (Posibles problemas al hacer Mocking)
Problema: No es posible mockear llamadas a métodos dentro de otros métodos del mismo archivo.
Ejemplo:
export function foo() { return "foo";}
export function bar() { return `${foo()} bar`;}import { vi } from "vitest";import * as mod from "./foobar.js";
vi.spyOn(mod, "foo");
describe("Mock foo", () => { it("should not mock foo inside bar", () => { expect(mod.bar()).toBe("foo bar"); // La llamada a foo dentro de bar no es mockeada });});4. Mocking File System (Simulación de sistema de archivos)
Principio: Puedes simular operaciones de sistema de archivos con memfs para evitar efectos secundarios en el sistema real.
Ejemplo:
import { vi } from "vitest";import { fs, vol } from "memfs";import { readFileSync } from "node:fs";
vi.mock("node:fs");vi.mock("node:fs/promises");
beforeEach(() => { vol.reset(); // Resetea el sistema de archivos en memoria});
test("should read file correctly", () => { const path = "/hello-world.txt"; fs.writeFileSync(path, "hello world");
const text = readFileSync(path); expect(text).toBe("hello world");});5. Mocking Requests (Simulación de Peticiones)
Principio: Puedes simular peticiones de red utilizando Mock Service Worker (MSW) para REST o GraphQL.
Ejemplo:
import { setupServer } from "msw/node";import { rest } from "msw";import { beforeAll, afterAll, afterEach, it, expect } from "vitest";
// Definición de la respuesta de la APIconst server = setupServer( rest.get("https://api.example.com/data", (req, res, ctx) => { return res(ctx.json({ message: "Success" })); }));
beforeAll(() => server.listen());afterAll(() => server.close());afterEach(() => server.resetHandlers());
test("should mock API call", async () => { const response = await fetch("https://api.example.com/data"); const data = await response.json();
expect(data.message).toBe("Success");});6. Mocking Timers (Simulación de temporizadores)
Principio: Usar temporizadores “falsos” con vi.useFakeTimers para acelerar las pruebas que dependen de setTimeout o setInterval.
Ejemplo:
import { vi } from "vitest";
function executeAfterTwoHours(func) { setTimeout(func, 1000 * 60 * 60 * 2); // 2 horas}
describe("Testing delayed execution", () => { beforeEach(() => { vi.useFakeTimers(); // Activa temporizadores falsos });
afterEach(() => { vi.restoreAllMocks(); // Restaura temporizadores reales });
it("should execute function after 2 hours", () => { const mockFunc = vi.fn(); executeAfterTwoHours(mockFunc); vi.runAllTimers(); // Avanza todos los temporizadores expect(mockFunc).toHaveBeenCalledTimes(1); // Se ejecuta una vez });});7. Mocking Classes (Simulación de Clases)
Principio: Puedes mockear clases completas utilizando vi.fn.
Ejemplo:
class Dog { constructor(name) { this.name = name; }
speak() { return "bark"; }}
const DogMock = vi.fn(() => { return { speak: vi.fn(() => "woof") };});
describe("Mocking a class", () => { test("should mock class methods", () => { const dog = new DogMock("Rex"); expect(dog.speak()).toBe("woof"); // Método speak está mockeado });});Vitest UI
Vitest está impulsado por Vite y utiliza un servidor de desarrollo cuando ejecuta las pruebas. Esto permite que Vitest proporcione una interfaz de usuario (UI) atractiva para ver e interactuar con las pruebas. La UI de Vitest es opcional y requiere instalación adicional:
npm i -D @vitest/uiPara iniciar las pruebas con la UI, usa el siguiente comando:
vitest --uiUsar la UI como Reportero
La UI también se puede usar como reportero. Para generar un informe HTML y previsualizar los resultados de las pruebas, agrega el reportero ‘html’ en la configuración de Vitest:
export default { test: { reporters: ["html"], },};Puedes ver tu informe de cobertura en la UI de Vitest. Consulta “Vitest UI Coverage” para más detalles.
⚠️ Advertencia
Si deseas seguir viendo cómo se ejecutan las pruebas en tiempo real en la terminal, no olvides agregar el reportero predeterminado en la opción reporters:
["default", "html"];☝️ Consejo
Para previsualizar tu informe HTML, puedes usar el comando Vite preview:
npx vite preview --outDir ./htmlTambién puedes configurar la salida con la opción outputFile, especificando la ruta .html, como ./html/index.html (valor predeterminado)
In-Source Testing
Vitest permite ejecutar pruebas directamente dentro del código fuente, similar a las pruebas de módulos en Rust. Esto permite que las pruebas compartan el mismo cierre que las implementaciones, lo que facilita la prueba de estados privados sin necesidad de exportarlos. Además, proporciona un ciclo de retroalimentación más cercano para el desarrollo.
⚠️ Advertencia
Este enfoque es para escribir pruebas dentro del código fuente. Si necesitas escribir pruebas en archivos separados, consulta la guía “Writing Tests”.
Configuración
-
Escribe Pruebas en el Código Fuente:
Para comenzar, añade un bloque
if (import.meta.vitest)al final de tu archivo fuente y escribe pruebas dentro de él. Por ejemplo:src/index.ts // La implementaciónexport function add(...args: number[]) {return args.reduce((a, b) => a + b, 0);}// Pruebas dentro del código fuenteif (import.meta.vitest) {const { it, expect } = import.meta.vitest;it("add", () => {expect(add()).toBe(0);expect(add(1)).toBe(1);expect(add(1, 2, 3)).toBe(6);});} -
Configura Vitest:
Actualiza la configuración de
includeSourceenvitest.config.tspara incluir los archivos bajosrc/:vitest.config.ts import { defineConfig } from "vitest/config";export default defineConfig({test: {includeSource: ["src/**/*.{js,ts}"],},}); -
Ejecuta las Pruebas:
Usa el comando siguiente para comenzar a ejecutar las pruebas:
Terminal window $ npx vitest
Compilación para Producción
Para la compilación en producción, configura la opción define en el archivo de configuración para que el empaquetador elimine el código innecesario. Por ejemplo, con Vite:
import { defineConfig } from "vitest/config";
export default defineConfig({ test: { includeSource: ["src/**/*.{js,ts}"], }, define: { "import.meta.vitest": "undefined", },});Otros Empaquetadores
- Unbuild
- Rollup
Soporte para TypeScript
Para añadir soporte de TypeScript para import.meta.vitest, agrega vitest/importMeta en tu archivo tsconfig.json:
{ "compilerOptions": { "types": ["vitest/importMeta"] }}Esta funcionalidad es útil para:
- Pruebas unitarias de funciones pequeñas o utilidades.
- Prototipos.
- Aserciones en línea.
Recomendación: Para pruebas más complejas, como componentes o pruebas de extremo a extremo (E2E), utiliza archivos de prueba separados.
Test Enviroment
Vitest proporciona una opción de entorno para ejecutar el código dentro de un entorno específico. Puedes modificar el comportamiento del entorno con la opción environmentOptions.
Por defecto, puedes usar estos entornos:
- node es el entorno por defecto.
- jsdom emula un entorno de navegador proporcionando la API del navegador, usa el paquete jsdom.
- happy-dom emula un entorno de navegador proporcionando la API del navegador y se considera más rápido que jsdom, aunque le faltan algunas APIs, usa el paquete happy-dom.
- edge-runtime emula el entorno edge-runtime de Vercel, usa el paquete
@edge-runtime/vm.
Información
Cuando se usan los entornos jsdom o happy-dom, Vitest sigue las mismas reglas que Vite al importar CSS y recursos. Si la importación de una dependencia externa falla con el error de extensión desconocida .css, necesitas incluir toda la cadena de importación manualmente añadiendo todos los paquetes a server.deps.external. Por ejemplo, si el error ocurre en el paquete-3 en esta cadena de importación: código fuente -> paquete-1 -> paquete-2 -> paquete-3, necesitas añadir los tres paquetes a server.deps.external.
Desde Vitest 2.0.4, la importación de CSS y recursos dentro de dependencias externas se resuelve automáticamente.
⚠️ Advertencia
Los “Entornos” solo existen cuando se ejecutan pruebas en Node.js. El entorno browser no se considera un entorno en Vitest.
Si quieres ejecutar parte de tus pruebas en el Modo de Navegador, puedes crear un proyecto de espacio de trabajo.
Entornos para Archivos Específicos
Cuando configures la opción environment en tu configuración, se aplicará a todos los archivos de prueba en tu proyecto. Para tener un control más granular, puedes usar comentarios de control para especificar el entorno para archivos específicos. Los comentarios de control son comentarios que comienzan con @vitest-environment seguidos del nombre del entorno:
// @vitest-environment jsdomimport { expect, test } from "vitest";test("test", () => { expect(typeof window).not.toBe("undefined");});También puedes configurar la opción environmentMatchGlobs especificando el entorno basado en los patrones glob.
Entorno Personalizado
Puedes crear tu propio paquete para extender el entorno de Vitest. Para hacerlo, crea un paquete con el nombre vitest-environment-${name} o especifica una ruta a un archivo JS/TS válido. Ese paquete debe exportar un objeto con la forma de Environment:
import type { Environment } from "vitest";export default <Environment>{ name: "custom", transformMode: "ssr", // opcional, solo si soportas el "experimental-vm" async setupVM() { const vm = await import("node:vm"); const context = vm.createContext(); return { getVmContext() { return context; }, teardown() { // llamado después de ejecutar todas las pruebas con este entorno }, }; }, setup() { // configuración personalizada return { teardown() { // llamado después de ejecutar todas las pruebas con este entorno }, }; },};⚠️ Advertencia
Vitest requiere la opción transformMode en el objeto del entorno. Debe ser igual a ssr o web. Este valor determina cómo los plugins transformarán el código fuente. Si está configurado como ssr, los hooks del plugin recibirán ssr: true al transformar o resolver archivos. De lo contrario, ssr se establecerá en false.
También tienes acceso a los entornos predeterminados de Vitest a través de la entrada vitest/environments:
import { builtinEnvironments, populateGlobal } from "vitest/environments";console.log(builtinEnvironments); // { jsdom, happy-dom, node, edge-runtime }Vitest también proporciona la función utilitaria populateGlobal, que se puede usar para mover propiedades de un objeto al espacio de nombres global:
interface PopulateOptions { // Define si las funciones normales deben añadirse al espacio de nombres global}Common Errors
- Cannot find module ’./relative-path’
Si recibes un error indicando que no se puede encontrar un módulo, podría deberse a varias razones:
-
Error tipográfico en la ruta: Asegúrate de que la ruta sea correcta.
-
Uso de
baseUrlentsconfig.json: Por defecto, Vite no consideratsconfig.json. Si dependes de este comportamiento, necesitas instalarvite-tsconfig-paths:import { defineConfig } from "vitest/config";import tsconfigPaths from "vite-tsconfig-paths";export default defineConfig({plugins: [tsconfigPaths()],}); -
Reescribir rutas para que no sean relativas a la raíz:
import helpers from 'src/helpers'import helpers from '../src/helpers' -
Evita alias relativos: Vite trata los alias relativos como relativos al archivo de importación, no a la raíz del proyecto.
import { defineConfig } from "vitest/config";export default defineConfig({test: {alias: {"@/": "./src/","@/": new URL("./src/", import.meta.url).pathname,},},});
- Cannot mock ”./mocked-file.js” because it is already loaded
Este error ocurre cuando vi.mock se llama en un módulo que ya fue cargado. Vitest lanza este error porque la llamada no tiene efecto al preferir los módulos almacenados en caché.
- Recuerda que
vi.mocksiempre se eleva (“hoisted”): esto significa que el módulo fue cargado antes de que el archivo de prueba comenzara a ejecutarse, probablemente en un archivo de configuración. - Para solucionar el error, elimina la importación o limpia la caché al final del archivo de configuración:
setupFile.ts import { vi } from "vitest";import { sideEffect } from "./mocked-file.js";sideEffect();vi.resetModules();
- Failed to terminate worker
Este error puede ocurrir cuando fetch de NodeJS se usa con pool: 'threads'. Este problema está siendo rastreado en el issue #3077.
Solución temporal: Cambia a pool: 'forks' o pool: 'vmForks'.
import { defineConfig } from "vitest/config";
export default defineConfig({ test: { pool: "forks", },});- Segfaults and native code errors
Ejecutar módulos nativos de NodeJS con pool: 'threads' puede ocasionar errores crípticos provenientes del código nativo:
- Segmentation fault (core dumped)
- thread
<unnamed>panicked at ‘assertion failed’ - Abort trap: 6
- internal error: entered unreachable code
Esto generalmente significa que el módulo nativo no está diseñado para ser seguro en multi-hilos.
Solución temporal: Cambia a pool: 'forks', que ejecuta los casos de prueba en múltiples procesos hijos (node:child_process) en lugar de múltiples hilos (node:worker_threads).
import { defineConfig } from "vitest/config";
export default defineConfig({ test: { pool: "forks", },});