Testing
⚙️ Testing de interacción con
🧠 Comparativa rápida:
Uso de jest-dom y Testing Library en entornos React
🧠 Conceptos clave
- Testing Library es un conjunto de utilidades que facilitan las pruebas de componentes React desde la perspectiva del usuario final, priorizando el comportamiento sobre la implementación interna.
- jest-dom añade matchers personalizados a Jest (como
toBeInTheDocument,toHaveTextContent,toHaveStyle) para verificar el DOM de forma más semántica. - Esta combinación (React Testing Library + jest-dom + Jest) constituye el estándar moderno para testeo de interfaces.
- Se centra en la experiencia del usuario (UX) y no en detalles internos del componente.
⚙️ Instalación y configuración
npm install --save-dev @testing-library/react @testing-library/jest-dom @testing-library/user-event jest
`
Archivo: jest.setup.ts
import '@testing-library/jest-dom'
Archivo: jest.config.ts
export default {
preset: 'ts-jest',
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
'\\.(css|scss)$': 'identity-obj-proxy',
},
}
🧩 Ejemplo básico
// src/components/Saludo.tsx
import React from 'react'
export const Saludo = ({ nombre }: { nombre: string }) => {
return <h1 data-testid="titulo">Hola, {nombre} 👋</h1>
}
// src/components/Saludo.test.tsx
import { render, screen } from '@testing-library/react'
import { Saludo } from './Saludo'
describe('Saludo', () => {
test('muestra el nombre correctamente', () => {
render(<Saludo nombre="Eduardo" />)
expect(screen.getByText('Hola, Eduardo 👋')).toBeInTheDocument()
})
})
Resultado:
PASS src/components/Saludo.test.tsx
✓ muestra el nombre correctamente (12 ms)
⚙️ Testing de interacción con userEvent
// src/components/Contador.tsx
import React, { useState } from 'react'
export const Contador = () => {
const [count, setCount] = useState(0)
return (
<>
<p data-testid="contador">Valor: {count}</p>
<button onClick={() => setCount(count + 1)}>Incrementar</button>
</>
)
}
// src/components/Contador.test.tsx
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { Contador } from './Contador'
describe('Contador', () => {
test('incrementa al hacer clic', async () => {
const user = userEvent.setup()
render(<Contador />)
const boton = screen.getByRole('button', { name: /incrementar/i })
await user.click(boton)
expect(screen.getByTestId('contador')).toHaveTextContent('Valor: 1')
})
})
💡
userEventsimula acciones reales (click, tab, input, teclado, etc.), a diferencia defireEvent, que es más bajo nivel.
🧠 Ejemplo de pruebas de formularios
// src/components/LoginForm.tsx
import React, { useState } from 'react'
export const LoginForm = ({ onLogin }: { onLogin: (u: string, p: string) => void }) => {
const [user, setUser] = useState('')
const [pass, setPass] = useState('')
return (
<form
onSubmit={e => {
e.preventDefault()
onLogin(user, pass)
}}
>
<input placeholder="Usuario" value={user} onChange={e => setUser(e.target.value)} />
<input type="password" placeholder="Contraseña" value={pass} onChange={e => setPass(e.target.value)} />
<button type="submit">Entrar</button>
</form>
)
}
// src/components/LoginForm.test.tsx
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { LoginForm } from './LoginForm'
test('envía los datos correctos al login', async () => {
const mockLogin = vi.fn()
const user = userEvent.setup()
render(<LoginForm onLogin={mockLogin} />)
await user.type(screen.getByPlaceholderText('Usuario'), 'admin')
await user.type(screen.getByPlaceholderText('Contraseña'), '1234')
await user.click(screen.getByRole('button', { name: /entrar/i }))
expect(mockLogin).toHaveBeenCalledWith('admin', '1234')
})
🧩 Testing de componentes con hooks o efectos
// src/components/Loader.tsx
import React, { useEffect, useState } from 'react'
export const Loader = () => {
const [ready, setReady] = useState(false)
useEffect(() => {
const timer = setTimeout(() => setReady(true), 1000)
return () => clearTimeout(timer)
}, [])
return <div>{ready ? 'Listo ✅' : 'Cargando...'}</div>
}
// src/components/Loader.test.tsx
import { render, screen, waitFor } from '@testing-library/react'
import { Loader } from './Loader'
test('muestra "Listo ✅" después de 1s', async () => {
render(<Loader />)
expect(screen.getByText(/cargando/i)).toBeInTheDocument()
await waitFor(() => expect(screen.getByText(/listo/i)).toBeInTheDocument())
})
🧩
waitFores útil para pruebas con temporizadores, efectos o peticiones asíncronas.
🧠 Comparativa rápida: fireEvent vs userEvent
| Característica | fireEvent |
userEvent |
|---|---|---|
| Nivel de abstracción | Bajo | Alto |
| Simulación realista | ❌ No | ✅ Sí |
| Manejo de focus/blur | Manual | Automático |
| Eventos encadenados | No | Sí |
| Uso recomendado | Casos simples o internos | Simular acciones del usuario final |
💡 Buenas prácticas
- Usa
screenen lugar de destructuring derender()→ mejora legibilidad. - Evita probar implementaciones internas, céntrate en la experiencia del usuario.
- Usa
getByRoleogetByLabelTexten lugar degetByTestIdsiempre que sea posible. - Configura
jest.setup.tspara importar@testing-library/jest-domglobalmente. - Simula interacciones reales con
userEventen lugar defireEvent. - Usa
waitForofindBy...para elementos renderizados de forma asíncrona. - Añade estos tests en pipelines CICD y ejecuta con
--coverageen github actions.
📊 Ejemplo de integración en CICD
name: Jest UI Tests
on: [push, pull_request]
jobs:
test-ui:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm run test -- --coverage
📚 Recursos recomendados
- Testing Library Docs
- jest-dom Matchers
- user-event API
- Testing asíncrono en Jest con Promises y async/await
- Testeo de hooks personalizados en React con Jest
- Guía avanzada de Jest con mocks y spies
¿Te gusta este contenido? Suscríbete vía RSS