7.3 KiB
7.3 KiB
Tests del Frontend - Documentación
Este documento describe los tests implementados para el frontend del taller CI/CD.
Estructura de Tests
frontend/src/
├── utils/
│ ├── text.js # Función prettify()
│ └── text.test.js # Tests unitarios
├── services/
│ ├── api.js # Funciones de API
│ └── api.test.js # Tests con mocking
├── App.svelte # Componente principal
└── App.test.js # Tests de componente
Tecnologías Utilizadas
| Herramienta | Propósito |
|---|---|
| Vitest | Framework de testing (compatible con Vite) |
| @testing-library/svelte | Renderizado de componentes Svelte |
| jsdom | Simulación del DOM en Node.js |
1. Tests Unitarios: text.test.js
Objetivo
Testear funciones puras sin efectos secundarios. La función prettify() convierte strings en formato snake_case a Title Case.
Tests Implementados
| Test | Input | Output Esperado | Concepto |
|---|---|---|---|
| Conversión básica | 'hello_world' |
'Hello World' |
Transformación de texto |
| Múltiples palabras | 'cafe_con_leche' |
'Cafe Con Leche' |
Manejo de múltiples _ |
| Sin underscore | 'bocadillo' |
'Bocadillo' |
Edge case |
| String vacío | '' |
'' |
Boundary testing |
| Mayúsculas | 'TOSTADA_INTEGRAL' |
'Tostada Integral' |
Normalización |
Código Explicado
import { describe, it, expect } from 'vitest';
import { prettify } from './text';
describe('prettify', () => {
it('convierte snake_case a Title Case', () => {
// Dado un string con underscores
const input = 'hello_world';
// Cuando llamamos a prettify
const result = prettify(input);
// Entonces obtenemos Title Case
expect(result).toBe('Hello World');
});
});
¿Por qué este test es ideal para principiantes?
- Sin dependencias externas: No necesita mocking
- Determinístico: Mismo input = mismo output siempre
- Fácil de romper: Cambiar la función hace fallar el test inmediatamente
- Concepto claro: Input → Función → Output
2. Tests de API: api.test.js
Objetivo
Testear funciones que hacen llamadas HTTP sin depender de un servidor real.
Concepto Clave: Mocking
Mocking significa "simular" una dependencia externa. En este caso, simulamos fetch():
beforeEach(() => {
// Reemplazamos fetch global con una función simulada
vi.stubGlobal('fetch', vi.fn());
});
afterEach(() => {
// Restauramos el fetch original
vi.unstubAllGlobals();
});
Tests Implementados
| Test | Qué Verifica | Concepto de Testing |
|---|---|---|
getMenu() éxito |
Devuelve datos cuando API OK | Happy path |
getMenu() error |
Lanza excepción cuando API falla | Error handling |
getPrices() items |
Extrae .items del response |
Data transformation |
getPrices() vacío |
Devuelve [] si no hay items |
Defensive programming |
getCiStatus() |
Llama a /health |
Contract testing |
getBuildHistory() |
Llama a /builds |
Contract testing |
Código Explicado
describe('getMenu', () => {
it('devuelve datos del menu cuando la API responde correctamente', async () => {
// 1. ARRANGE: Preparamos el mock
const mockMenu = { starters: ['Sopa'], mains: ['Pescado'] };
fetch.mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve(mockMenu),
});
// 2. ACT: Ejecutamos la función
const result = await getMenu();
// 3. ASSERT: Verificamos el resultado
expect(fetch).toHaveBeenCalledWith('/taller/api/menu');
expect(result).toEqual(mockMenu);
});
});
¿Por qué este test es importante para CI/CD?
- Independencia: No necesita backend corriendo
- Rapidez: Se ejecuta en milisegundos
- Fiabilidad: No falla por problemas de red
- Contract testing: Verifica que la API se llama correctamente
3. Tests de Componente: App.test.js
Objetivo
Verificar que el componente Svelte renderiza correctamente y muestra la información esperada.
Concepto Clave: Testing Library
@testing-library/svelte renderiza componentes y permite buscar elementos como lo haría un usuario:
import { render, screen, waitFor } from '@testing-library/svelte';
import App from './App.svelte';
// Renderiza el componente
render(App);
// Busca elementos como un usuario
screen.getByText('Menú del día');
screen.getByRole('heading', { level: 1 });
Tests Implementados
| Test | Qué Verifica | Selector Usado |
|---|---|---|
| Título principal | H1 existe | getByRole('heading', { level: 1 }) |
| Secciones menú | Primeros/Segundos/Postres | getByText('Primeros') |
| Estado API | Muestra "Operativa" | getByText(/Operativa/) |
| Build number | Muestra "#42" | getByText('#42') |
| Precios | Muestra "Cafe", "Tostada" | getByText('Cafe') |
Código Explicado
describe('App.svelte', () => {
beforeEach(() => {
// Mockeamos fetch para cada endpoint
vi.stubGlobal('fetch', createMockFetch());
});
it('muestra las secciones del menu cuando carga', async () => {
// Renderizamos el componente
render(App);
// Esperamos a que cargue (es async)
await waitFor(() => {
expect(screen.getByText('Primeros')).toBeInTheDocument();
expect(screen.getByText('Segundos')).toBeInTheDocument();
});
});
});
¿Por qué waitFor?
El componente hace llamadas async al montarse. waitFor espera hasta que:
- La condición se cumple, O
- Pasa el timeout (falla el test)
Ejecutar los Tests
Localmente
cd frontend
npm install
npm test
En modo watch (desarrollo)
npm test -- --watch
Con coverage
npm test -- --coverage
Integración con Jenkins
Los tests se ejecutan en el pipeline CI (Jenkinsfile.ci):
stage('Frontend: check & build') {
steps {
dir('frontend') {
sh '''
npm install
npm run check
npm test # ← Ejecuta estos tests
npm run build
'''
}
}
}
Flujo en Jenkins
1. Push/MR → 2. Jenkins detecta → 3. npm test → 4. ¿Pasa? → 5. Build
↓ NO
Pipeline FALLA
(estudiante corrige)
Cómo Romper los Tests (Ejercicio)
Para ver Jenkins fallar, prueba:
| Cambio | Test que Falla |
|---|---|
Cambiar prettify() para no capitalizar |
text.test.js |
Cambiar endpoint de /menu a /menus |
api.test.js |
| Eliminar sección "Primeros" del HTML | App.test.js |
Glosario
| Término | Definición |
|---|---|
| Unit Test | Test de una función/módulo aislado |
| Mock | Simulación de una dependencia |
| Assertion | Verificación de que algo es verdadero |
| Happy Path | Escenario donde todo funciona bien |
| Edge Case | Escenario límite o inusual |
| Coverage | % del código ejecutado por tests |