Ejercicios resueltos — Capítulo 16¶
Volver al índice · Capítulo original
Ejercicio 1: Tests de formatearPrecio¶
Escribe tests unitarios para una función
formatearPrecio(n).
Solución¶
// precios.js
export function formatearPrecio(n) {
return new Intl.NumberFormat('es-ES', {
style: 'currency',
currency: 'EUR',
}).format(n);
}
// precios.test.js
import { describe, it, expect } from 'vitest';
import { formatearPrecio } from './precios.js';
describe('formatearPrecio', () => {
it('formatea un número entero', () => {
expect(formatearPrecio(10)).toContain('10,00');
});
it('formatea decimales', () => {
expect(formatearPrecio(29.99)).toContain('29,99');
});
it('incluye el símbolo del euro', () => {
expect(formatearPrecio(100)).toContain('€');
});
it('maneja el cero', () => {
expect(formatearPrecio(0)).toContain('0,00');
});
});
Ejercicio 2: Tests de validarEmail¶
Cubre con Vitest una función
validarEmail(email).
Solución¶
// validacion.js
export function validarEmail(email) {
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return regex.test(email);
}
// validacion.test.js
import { describe, it, expect } from 'vitest';
import { validarEmail } from './validacion.js';
describe('validarEmail', () => {
it('acepta emails válidos', () => {
expect(validarEmail('usuario@ejemplo.com')).toBe(true);
expect(validarEmail('a.b@sub.dominio.es')).toBe(true);
});
it('rechaza emails sin @', () => {
expect(validarEmail('usuario.com')).toBe(false);
});
it('rechaza emails sin dominio', () => {
expect(validarEmail('usuario@')).toBe(false);
});
it('rechaza emails con espacios', () => {
expect(validarEmail('usuario @ejemplo.com')).toBe(false);
});
it('rechaza strings vacíos', () => {
expect(validarEmail('')).toBe(false);
});
});
Ejercicio 3: Test de formulario con Testing Library¶
Rellena y envía un formulario con Testing Library.
Solución¶
<form>
<label for="nombre">Nombre</label>
<input type="text" id="nombre" required>
<label for="email">Email</label>
<input type="email" id="email" required>
<button type="submit">Enviar</button>
</form>
import { screen } from '@testing-library/dom';
import userEvent from '@testing-library/user-event';
import { it, expect, vi } from 'vitest';
it('rellena y envía el formulario', async () => {
document.body.innerHTML = `
<form>
<label for="nombre">Nombre</label>
<input type="text" id="nombre" required>
<label for="email">Email</label>
<input type="email" id="email" required>
<button type="submit">Enviar</button>
</form>
`;
const enviar = vi.fn();
const form = document.querySelector('form');
form.addEventListener('submit', (e) => {
e.preventDefault();
enviar();
});
await userEvent.type(screen.getByLabelText('Nombre'), 'Urukais');
await userEvent.type(screen.getByLabelText('Email'), 'urukaisk@gmail.com');
await userEvent.click(screen.getByRole('button', { name: 'Enviar' }));
expect(enviar).toHaveBeenCalledTimes(1);
});
Ejercicio 4: Mock de API con MSW¶
Mockea una API con MSW y prueba un componente que la consume.
Solución¶
// mocks/handlers.js
import { http, HttpResponse } from 'msw';
export const handlers = [
http.get('/api/usuarios', () => {
return HttpResponse.json([
{ id: 1, nombre: 'Urukais' },
{ id: 2, nombre: 'Ana' },
]);
}),
];
// mocks/server.js
import { setupServer } from 'msw/node';
import { handlers } from './handlers.js';
export const server = setupServer(...handlers);
// usuarios.test.js
import { beforeAll, afterAll, afterEach, it, expect } from 'vitest';
import { server } from './mocks/server.js';
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
it('carga la lista de usuarios', async () => {
const res = await fetch('/api/usuarios');
const usuarios = await res.json();
expect(usuarios).toHaveLength(2);
expect(usuarios[0].nombre).toBe('Urukais');
});
Ejercicio 5: Test E2E con Playwright¶
Escribe un test E2E para "buscar producto → añadir al carrito".
Solución¶
// e2e/carrito.spec.js
import { test, expect } from '@playwright/test';
test('buscar producto y añadir al carrito', async ({ page }) => {
await page.goto('/');
// Buscar
await page.getByPlaceholder('Buscar productos').fill('camiseta');
await page.getByRole('button', { name: 'Buscar' }).click();
// Ver resultados
await expect(page.getByText('Camiseta')).toBeVisible();
// Añadir al carrito
await page.getByRole('button', { name: 'Añadir al carrito' }).first().click();
// Verificar contador
await expect(page.getByTestId('carrito-contador')).toHaveText('1');
});
Ejercicio 6: Cobertura¶
Configura
npm run test:coveragey analiza el informe.
Solución¶
Instala:
npm install -D @vitest/coverage-v8
Añade a package.json:
{
"scripts": {
"test:coverage": "vitest run --coverage"
}
}
Configura en vite.config.js:
export default defineConfig({
test: {
coverage: {
provider: 'v8',
reporter: ['text', 'html'],
exclude: ['**/*.test.js', '**/node_modules/**'],
},
},
});
Ejecuta:
npm run test:coverage
Ejercicio 7: GitHub Action de tests¶
Añade un workflow que ejecute los tests en cada PR.
Solución¶
.github/workflows/test.yml:
name: Tests
on:
push:
branches: [main]
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npm run test:run