Testing unitarios
1. ¿Qué vamos a probar? #
Ahora mismo tenemos algo como:
1const fecha = dayjs().format("DD/MM/YYYY HH:mm");
El problema es que está metido directamente dentro del código de la interfaz. Vamos a extraer esa lógica a una función:
1Obtener una fecha 2 ↓ 3formatearla 4 ↓ 5"16/09/2026 16:05"
Un test unitario comprueba una pequeña unidad de nuestro programa, normalmente una función, de forma aislada.
2. Crear utils.js #
Creamos:
1src/ 2├── main.js 3├── style.css 4└── utils.js
En src/utils.js:
1import dayjs from "dayjs"; 2 3export function formatearFecha(fecha = new Date()) { 4 return dayjs(fecha).format("DD/MM/YYYY HH:mm"); 5}
Ahora podemos utilizarla desde main.js:
1import "./style.css"; 2import { formatearFecha } from "./utils.js"; 3 4const input = document.querySelector("#tarea"); 5const boton = document.querySelector("#añadir"); 6const lista = document.querySelector("#lista"); 7 8boton.addEventListener("click", () => { 9 const li = document.createElement("li"); 10 11 const fecha = formatearFecha(); 12 13 li.textContent = `${input.value} - ${fecha}`; 14 15 lista.appendChild(li); 16 17 input.value = ""; 18});
Esto ya introduce separación de responsabilidades:
1main.js 2 │ 3 ├── interfaz 4 ├── eventos 5 └── DOM 6 7utils.js 8 │ 9 └── lógica reutilizable
3. Instalar Vitest #
Ahora queremos probar formatearFecha().
1npm install --save-dev vitest
O la forma corta:
1npm i -D vitest
Y aquí volvería inmediatamente a package.json.
Antes:
1"devDependencies": { 2 "vite": "..." 3}
Después:
1"devDependencies": { 2 "vite": "...", 3 "vitest": "..." 4}
¿Por qué Vitest está en
devDependenciesy no endependencies?
Porque necesitamos Vitest para desarrollar y comprobar la aplicación, pero el usuario final no necesita Vitest para utilizar TaskApp.
4. Crear nuestro primer test #
Podemos crear:
1src/ 2├── main.js 3├── style.css 4├── utils.js 5└── utils.test.js
En utils.test.js:
1import { describe, it, expect } from "vitest"; 2import { formatearFecha } from "./utils.js"; 3 4describe("formatearFecha", () => { 5 6 it("formatea correctamente una fecha", () => { 7 8 const fecha = new Date(2026, 8, 16, 14, 30); 9 10 const resultado = formatearFecha(fecha); 11 12 expect(resultado).toBe("16/09/2026 14:30"); 13 14 }); 15 16});
He usado el constructor local new Date(2026, 8, 16, 14, 30) para evitar que una Z/UTC nos cambie la hora según la zona horaria.
Y ojo con esto:
1new Date(2026, 8, 16)
El 8 significa septiembre, porque en este constructor JavaScript numera los meses desde 0.
5. Añadir el script #
En package.json:
1"scripts": { 2 "dev": "vite", 3 "build": "vite build", 4 "preview": "vite preview", 5 "test": "vitest" 6}
Ahora:
1npm test
Vitest ejecutará los tests.
También podemos ejecutar una sola vez, algo útil posteriormente para CI:
1npm test -- --run
6. Hacer que el test falle intencionadamente #
Esta parte la haría sí o sí delante de los alumnos.
Cambiamos:
1expect(resultado).toBe("16/09/2026 14:30");
por:
1expect(resultado).toBe("16-09-2026 14:30");
Ejecutamos:
1npm test
💥 Test fallido.
Así ven que no estamos simplemente ejecutando código: estamos expresando qué resultado esperamos.
Volvemos a:
1expect(resultado).toBe("16/09/2026 14:30");
✅ Test superado.
7. Añadir varios casos #
Ahora podemos hacer algo un poco más interesante:
1import { describe, it, expect } from "vitest"; 2import { formatearFecha } from "./utils.js"; 3 4describe("formatearFecha", () => { 5 6 it("formatea correctamente una fecha", () => { 7 const fecha = new Date(2026, 8, 16, 14, 30); 8 9 expect(formatearFecha(fecha)) 10 .toBe("16/09/2026 14:30"); 11 }); 12 13 it("añade ceros a días y meses de una cifra", () => { 14 const fecha = new Date(2026, 0, 5, 9, 7); 15 16 expect(formatearFecha(fecha)) 17 .toBe("05/01/2026 09:07"); 18 }); 19 20});
8. ¿Y cómo probamos "la fecha actual"? #
Nuestra función permite:
1formatearFecha();
Eso utiliza:
1new Date()
Pero no conviene hacer un test esperando la hora real del ordenador, porque el resultado cambia continuamente.
Vitest permite simular el reloj.
Podemos hacer:
1import { describe, it, expect, vi, afterEach } from "vitest"; 2import { formatearFecha } from "./utils.js"; 3 4describe("formatearFecha", () => { 5 6 afterEach(() => { 7 vi.useRealTimers(); 8 }); 9 10 it("formatea correctamente una fecha", () => { 11 const fecha = new Date(2026, 8, 16, 14, 30); 12 13 expect(formatearFecha(fecha)) 14 .toBe("16/09/2026 14:30"); 15 }); 16 17 it("utiliza la fecha actual cuando no recibe una fecha", () => { 18 19 vi.useFakeTimers(); 20 21 vi.setSystemTime( 22 new Date(2026, 8, 16, 16, 30) 23 ); 24 25 expect(formatearFecha()) 26 .toBe("16/09/2026 16:30"); 27 28 }); 29 30});
9. La estructura del proyecto ya empieza a ser interesante #
1taskapp/ 2│ 3├── index.html 4├── package.json 5├── package-lock.json 6├── .gitignore 7│ 8└── src/ 9 ├── main.js 10 ├── style.css 11 ├── utils.js 12 └── utils.test.js
Y package.json empieza a contar la historia del proyecto:
1{ 2 "scripts": { 3 "dev": "vite", 4 "build": "vite build", 5 "preview": "vite preview", 6 "test": "vitest" 7 }, 8 9 "dependencies": { 10 "dayjs": "..." 11 }, 12 13 "devDependencies": { 14 "vite": "...", 15 "vitest": "..." 16 } 17}