docs(frontend): añadir documentación de tests
This commit is contained in:
273
frontend/TESTS.md
Normal file
273
frontend/TESTS.md
Normal file
@@ -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 |
|
||||||
Reference in New Issue
Block a user