Ya tienes el porqué del capítulo anterior. Toca el cómo, y empieza por una idea que choca al principio: el testing vive en la terminal, no en el navegador. No abres una página para testear; lanzas un comando, un programa corre tus tests y te devuelve un informe de qué pasa y qué falla. Ese programa es el test runner, y el del ecosistema de Vite es Vitest.
Qué es Vitest y por qué este#
Un test runner hace tres cosas: encuentra tus ficheros de test, los ejecuta y te informa de cuáles pasan (verde) y cuáles fallan (rojo). Tú escribes los tests; él los corre.
En 2026, en un proyecto con Vite (el que montaste en el Nivel 4), el runner por defecto es Vitest, y por buenas razones:
- Es rápido. Reaprovecha la misma maquinaria de Vite que ya usa tu proyecto para transformar el código, así que no monta un sistema aparte.
- Entiende TypeScript y los módulos ESM sin configurar nada. Escribes
importy.tsy funciona; no hay que montar un transpilador a mano. - Trae modo watch. Se queda vigilando y vuelve a correr solo lo que tu cambio afecta.
- Su API es casi idéntica a la de Jest, el runner clásico. Lo que aprendas aquí te sirve si algún día caes en un proyecto con Jest: cambian cuatro detalles, no el modelo mental.
Instalarlo#
Vitest se instala como dependencia de desarrollo: lo necesitas para programar y para tu pipeline, pero no forma parte de lo que se despliega a los usuarios.
# -D = devDependency: herramienta de desarrollo, no va al bundle de producción.
pnpm add -D vitestRecuerda la distinción del Nivel 4: en dependencies va lo que se ejecuta en producción (React,
Zod); en devDependencies, lo que solo usas tú al desarrollar (Vitest, ESLint, los tipos).
Los scripts de npm#
En vez de teclear el comando completo cada vez, lo dejas con nombre en package.json:
{
"scripts": {
"test": "vitest run",
"test:watch": "vitest",
"coverage": "vitest run --coverage"
}
}pnpm test→ corre la suite una vez y termina (vitest run). Es el que usará tu CI.pnpm test:watch→vitesta secas se queda en modo watch.pnpm coverage→ corre y además mide la cobertura (cuánto código tocan tus tests; lo veremos a fondo al final del nivel).
La convención .test.ts#
Vitest no necesita que le listes tus tests: los reconoce por el nombre del fichero. Cualquier
fichero acabado en .test.ts (o .spec.ts) se considera un test y se ejecuta solo.
Lo habitual es colocar el test junto al fichero que prueba, con el mismo nombre:
src/
motor.ts ← el código
motor.test.ts ← sus tests, al ladoAsí, al abrir la carpeta, ves de un vistazo qué está probado y qué no. (Hay quien los agrupa en
una carpeta tests/ aparte; las dos formas valen, pero co-locar es lo más común en frontend.)
Tu primer test en verde#
Crea un fichero suma.test.ts con el test más simple que existe: comprobar que dos más dos son
cuatro. No prueba nada de tu dominio todavía; solo confirma que la maquinaria corre.
// suma.test.ts
// describe/it/expect se importan desde "vitest" (sin globals mágicos).
import { describe, it, expect } from "vitest";
// describe agrupa los tests de un mismo tema bajo un título.
describe("primer test", () => {
// it declara un caso concreto, descrito en lenguaje llano.
it("2 + 2 son 4", () => {
// expect afirma el resultado; toBe lo compara con lo esperado.
expect(2 + 2).toBe(4);
});
});Lánzalo con pnpm test y verás algo así en la terminal:
✓ suma.test.ts (1 test) 2ms
✓ primer test > 2 + 2 son 4
Test Files 1 passed (1)
Tests 1 passed (1)Ese ✓ en verde es tu primer test pasando. Enhorabuena: la red de seguridad está colgada.
Modo watch: el bucle apretado#
Mientras desarrollas, no lances pnpm test a mano una y otra vez. Arranca pnpm test:watch y
déjalo corriendo en una terminal: cada vez que guardas un fichero, Vitest vuelve a correr solo
los tests afectados por tu cambio, en milisegundos. Escribes código, guardas, miras el verde o
el rojo, sigues. Ese bucle inmediato es lo que hace que testear deje de ser un peaje y se vuelva
parte natural de programar.
Configurar lo justo#
Vitest funciona sin configuración: si tienes un vite.config.ts, lo lee. Pero declarar un
vitest.config.ts deja tus decisiones por escrito y es donde crecerá la config más adelante:
// vitest.config.ts
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
// entorno node: lógica pura, sin DOM. (Para componentes, "jsdom"; eso es el Nivel 8.)
environment: "node",
// qué ficheros son tests.
include: ["**/*.test.ts"],
// globals false: importamos describe/it/expect, sin variables globales.
globals: false,
},
});La opción que importa ahora es environment. Como en este nivel probamos lógica pura —el
motor del Team Builder, funciones que entran datos y salen datos— basta "node": no hay
navegador, ni window, ni DOM. Cuando en el Nivel 8 pruebes componentes de React, cambiarás a
"jsdom", que simula un navegador en memoria. Aquí no te hace falta.
Comprueba lo que sabes#
Pregunta 1 de 5
¿Qué es, en una frase, un test runner como Vitest?
Tu turno#
Este ejercicio se hace en local: el testing vive en tu terminal. Clona la carpeta del ejercicio, instala Vitest y deja tu primer test en verde. Cuando lo tengas (o si te atascas), despliega las soluciones y fíjate en el salto de un nivel al siguiente.
Ejercicio · hazlo en local
Pon Vitest en marcha
Parte de un proyecto mínimo (motor.ts ya escrito, package.json sin Vitest). Instala Vitest, añade los scripts, y deja un primer test en verde con pnpm test. Luego sube de nivel: prueba una función real del motor y configura Vitest de forma explícita.
Paso 1: Primer test en verde
- Instalas Vitest como dependencia de desarrollo (pnpm add -D vitest).
- Añades el script "test": "vitest run" al package.json.
- Creas un fichero .test.ts con un test trivial (una suma) y pnpm test lo muestra en verde.
Paso 2: Probar código real, con buen flujo
- Añades también "test:watch": "vitest" y "coverage": "vitest run --coverage".
- Escribes un test co-locado (motor.test.ts junto a motor.ts) que prueba una función de verdad: que enriquecer calcula bien el winrate.
- Importas describe/it/expect explícitamente desde "vitest" (sin depender de globals).
Paso 3: Configuración explícita
- Creas un vitest.config.ts con defineConfig: environment "node", include de los *.test.ts y globals: false.
- Compruebas que un test que afirma algo FALSO se pone en rojo, y lees el mensaje de error antes de arreglarlo: confirmas que la red de seguridad realmente avisa.
- El proyecto queda con un solo comando para correr todo (pnpm test) y otro para el bucle de desarrollo (pnpm test:watch).
Cómo hacerlo en local
Clona el repositorio del curso, entra en la carpeta del ejercicio y abre el
index.html en tu navegador. Toda tu solución va en
solucion.js.
git clone <repo>
cd exercises/nivel-6/configurar-vitest
# abre index.html en el navegador y edita solucion.js Ver soluciones
// suma.test.ts — el primer test, lo más simple posible.
// Su único objetivo: confirmar que Vitest está instalado y corre de verdad.
import { describe, it, expect } from "vitest";
// describe agrupa los tests de un mismo tema bajo un título legible.
describe("primer test", () => {
// it declara UN caso concreto, descrito en lenguaje llano.
it("2 + 2 son 4", () => {
// expect afirma lo que debe pasar; toBe compara el valor con el esperado.
expect(2 + 2).toBe(4);
});
}); Por qué este nivel
- El test más simple posible: una suma. No prueba nada del dominio, y está bien: su único trabajo es confirmar que la instalación funciona y que pnpm test corre de verdad.
- describe/it/expect importados desde "vitest": la sintaxis literal que usarás el resto del curso.
- Su límite: no prueba TU código. Es un "hola mundo" del testing; el siguiente paso es apuntar a una función real.
// motor.test.ts — co-locado JUNTO a motor.ts (misma carpeta, sufijo .test).
// Ya no probamos una suma de juguete, sino una función real del motor.
import { describe, it, expect } from "vitest";
// Importamos la función bajo prueba y su tipo desde el módulo vecino.
import { enriquecer, type Heroe } from "./motor";
describe("enriquecer", () => {
it("calcula el winrate como victorias entre partidas, en porcentaje", () => {
// Anotamos el tipo para que rol sea el union "Daño" | ..., no un string suelto.
const heroes: Heroe[] = [
{ nombre: "Genji", rol: "Daño", partidas: 10, victorias: 6 },
];
// Ejecutamos la función que queremos probar.
const resultado = enriquecer(heroes);
// 6 victorias de 10 partidas → 60% de winrate.
expect(resultado[0].winrate).toBe(60);
});
}); Por qué es mejor que el anterior
- Ahora el test prueba una función del motor: que enriquecer calcula el winrate. Eso ya aporta seguridad real sobre el dominio.
- El fichero va co-locado (motor.test.ts junto a motor.ts) y importa lo que prueba con una ruta relativa: quien abre la carpeta ve el código y su test juntos.
- Anotar el tipo (Heroe[]) evita que rol se ensanche a string y el objeto deje de encajar: un detalle de TypeScript que ya conoces del Nivel 5, aquí aplicado al dato de prueba.
// vitest.config.ts — configuración explícita del runner.
// Sin este fichero Vitest ya funciona (lee tu config de Vite), pero declararlo
// deja claras las decisiones y es donde crecerá la config cuando llegue React.
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
// entorno node: probamos lógica pura, sin DOM. El navegador (jsdom) llega en el Nivel 8.
environment: "node",
// qué ficheros son tests: cualquier *.test.ts junto al código que prueban.
include: ["**/*.test.ts"],
// globals false: importamos describe/it/expect explícitamente desde "vitest".
// Es lo recomendado: el origen de cada cosa queda a la vista, sin magia global.
globals: false,
},
}); Por qué es mejor que el anterior
- La configuración explícita documenta las decisiones: entorno node (lógica pura), qué ficheros son tests y globals desactivado. Sin este fichero Vitest también funciona, pero aquí es donde crecerá la config cuando llegue React.
- globals: false es una elección consciente: preferimos importar describe/it/expect a tenerlos como variables globales mágicas. El origen de cada cosa queda a la vista.
- El paso que más enseña no se ve en el fichero: comprobar que un test FALSO se pone en rojo. Una suite que solo has visto en verde no demuestra que proteja; verla fallar a propósito sí.