From 33a629f47d9f86faf34f6ad9e5b0219bce056822 Mon Sep 17 00:00:00 2001 From: Abdulee Date: Wed, 4 Feb 2026 13:20:29 +0100 Subject: [PATCH] =?UTF-8?q?docs(frontend):=20a=C3=B1adir=20documentaci?= =?UTF-8?q?=C3=B3n=20de=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/TESTS.md | 273 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 273 insertions(+) create mode 100644 frontend/TESTS.md diff --git a/frontend/TESTS.md b/frontend/TESTS.md new file mode 100644 index 0000000..8bfabeb --- /dev/null +++ b/frontend/TESTS.md @@ -0,0 +1,273 @@ +# 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 + +```javascript +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? + +1. **Sin dependencias externas**: No necesita mocking +2. **Determinístico**: Mismo input = mismo output siempre +3. **Fácil de romper**: Cambiar la función hace fallar el test inmediatamente +4. **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()`: + +```javascript +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 + +```javascript +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: + +```javascript +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 + +```javascript +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: +1. La condición se cumple, O +2. Pasa el timeout (falla el test) + +--- + +## Ejecutar los Tests + +### Localmente + +```bash +cd frontend +npm install +npm test +``` + +### En modo watch (desarrollo) + +```bash +npm test -- --watch +``` + +### Con coverage + +```bash +npm test -- --coverage +``` + +--- + +## Integración con Jenkins + +Los tests se ejecutan en el pipeline CI (`Jenkinsfile.ci`): + +```groovy +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 |