Testing de Componentes en Svelte: Guía Práctica con Vitest y Testing Library
Descubre cómo configurar y escribir pruebas robustas para tus aplicaciones Svelte utilizando Vitest y Svelte Testing Library. Este tutorial cubre desde la configuración inicial hasta pruebas de eventos y asincronía.
Introducción al Testing en Svelte 🧪
El desarrollo web moderno exige aplicaciones robustas y libres de errores. Svelte, con su enfoque innovador y sin un DOM virtual pesado, ofrece una experiencia de desarrollo sumamente ligera y rápida. Sin embargo, la velocidad de desarrollo no debe comprometer la calidad. Aquí es donde entra en juego el testing automatizado.
En este tutorial completo, aprenderás a configurar un entorno de pruebas moderno utilizando Vitest (la alternativa ultrarrápida impulsada por Vite) y Svelte Testing Library, la librería estándar de la industria para probar componentes interactivos enfocándonos en el comportamiento del usuario final en lugar de en los detalles de implementación.
Requisitos Previos y Configuración del Entorno 🛠️
Antes de escribir nuestra primera prueba, necesitamos preparar nuestro proyecto Svelte. Asumiremos que ya tienes una aplicación Svelte (preferiblemente con Vite). Si estás comenzando desde cero, puedes crear un proyecto ejecutando los siguientes comandos en tu terminal:
npm create vite@latest mi-app-svelte -- --template svelte
cd mi-app-svelte
npm install
Instalación de Dependencias de Pruebas
Necesitamos instalar Vitest, @testing-library/svelte, jsdom para simular el navegador en entornos Node, y @testing-library/jest-dom para aserciones DOM enriquecidas.
Ejecuta el siguiente comando para instalar todo lo necesario como dependencias de desarrollo:
npm install -D vitest @testing-library/svelte @testing-library/jest-dom jsdom happy-dom
Configuración de Vitest
Crea un archivo llamado vitest.config.js en la raíz de tu proyecto para configurar Vitest y decirle que utilice un entorno simulado de navegador:
import { defineConfig } from 'vitest/config';
import { svelte } from '@sveltejs/vite-plugin-svelte';
export default defineConfig({
plugins: [svelte({ hot: !process.env.VITEST })],
test: {
environment: 'jsdom',
globals: true,
setupFiles: ['./src/setupTests.js']
}
});
Ahora, crea el archivo src/setupTests.js para importar las expectativas personalizadas de Jest-DOM:
import '@testing-library/jest-dom';
Finalmente, añade un script en tu package.json para ejecutar las pruebas fácilmente:
"scripts": {
"test": "vitest",
"test:ui": "vitest --ui"
}
Creando Nuestro Primer Componente y su Prueba 📝
Vamos a construir un componente simple pero interactivo llamado Contador.svelte. Este componente tendrá un título, un número que cambia al hacer clic en los botones de incremento y decremento, y un mensaje condicional.
El Componente: src/components/Contador.svelte
<script>
export let inicial = 0;
let cuenta = inicial;
function incrementar() {
cuenta += 1;
}
function decrementar() {
cuenta -= 1;
}
</script>
<div>
<h1 data-testid="titulo">Contador Interactivo</h1>
<p data-testid="valor-cuenta">Cuenta actual: {cuenta}</p>
<button on:click={incrementar}>Incrementar</button>
<button on:click={decrementar}>Decrementar</button>
{#if cuenta > 5}
<p data-testid="mensaje-alto">¡El número es alto!</p>
{/if}
</div>
La Prueba: src/components/Contador.test.js
Ahora escribiremos la prueba unitaria para verificar que el componente se renderiza correctamente y que los clics actualizan el estado visual.
import { render, fireEvent } from '@testing-library/svelte';
import { describe, it, expect } from 'vitest';
import Contador from './Contador.svelte';
describe('Componente Contador', () => {
it('se renderiza correctamente con el valor inicial por defecto', () => {
const { getByTestId } = render(Contador);
expect(getByTestId('titulo')).toHaveTextContent('Contador Interactivo');
expect(getByTestId('valor-cuenta')).toHaveTextContent('Cuenta actual: 0');
});
it('incrementa la cuenta cuando se hace clic en el botón Incrementar', async () => {
const { getByText, getByTestId } = render(Contador, { inicial: 3 });
const botonIncrementar = getByText('Incrementar');
await fireEvent.click(botonIncrementar);
expect(getByTestId('valor-cuenta')).toHaveTextContent('Cuenta actual: 4');
});
it('muestra un mensaje condicional cuando la cuenta es mayor a 5', async () => {
const { getByText, getByTestId, queryByTestId } = render(Contador, { inicial: 5 });
// Inicialmente no debe mostrar el mensaje
expect(queryByTestId('mensaje-alto')).toBeNull();
const botonIncrementar = getByText('Incrementar');
await fireEvent.click(botonIncrementar);
// Ahora la cuenta es 6, el mensaje debe aparecer
expect(getByTestId('mensaje-alto')).toBeInTheDocument();
expect(getByTestId('mensaje-alto')).toHaveTextContent('¡El número es alto!');
});
});
Pruebas de Formularios y Eventos Personalizados 📨
Los formularios son una parte fundamental de cualquier aplicación web moderna en Svelte. Vamos a crear un componente de formulario para probar la entrada de texto, el envío de datos y la emisión de eventos personalizados (dispatch).
El Componente: src/components/FormularioUsuario.svelte
<script>
import { createEventDispatcher } from 'svelte';
const dispatch = createEventDispatcher();
let nombre = '';
let email = '';
let error = '';
function handleSubmit() {
if (!nombre || !email) {
error = 'Todos los campos son obligatorios';
return;
}
error = '';
dispatch('enviar', { nombre, email });
}
</script>
<form on:submit|preventDefault={handleSubmit}>
<h2>Registro de Usuario</h2>
{#if error}
<p data-testid="error-msg" style="color: red;">{error}</p>
{/if}
<label for="nombre">Nombre:</label>
<input id="nombre" type="text" bind:value={nombre} />
<label for="email">Correo electrónico:</label>
<input id="email" type="email" bind:value={email} />
<button type="submit">Registrarse</button>
</form>
La Prueba: src/components/FormularioUsuario.test.js
import { render, fireEvent } from '@testing-library/svelte';
import { describe, it, expect, vi } from 'vitest';
import FormularioUsuario from './FormularioUsuario.svelte';
describe('Componente FormularioUsuario', () => {
it('muestra un mensaje de error si se envía vacío', async () => {
const { getByText, getByTestId } = render(FormularioUsuario);
const botonSubmit = getByText('Registrarse');
await fireEvent.click(botonSubmit);
expect(getByTestId('error-msg')).toHaveTextContent('Todos los campos son obligatorios');
});
it('emite el evento enviar con los datos correctos cuando se llena el formulario', async () => {
const { getByLabelText, getByText, component } = render(FormularioUsuario);
const mockHandler = vi.fn();
component.$on('enviar', mockHandler);
const inputNombre = getByLabelText('Nombre:');
const inputEmail = getByLabelText('Correo electrónico:');
const botonSubmit = getByText('Registrarse');
await fireEvent.input(inputNombre, { target: { value: 'Ana Gómez' } });
await fireEvent.input(inputEmail, { target: { value: 'ana@example.com' } });
await fireEvent.click(botonSubmit);
expect(mockHandler).toHaveBeenCalledTimes(1);
expect(mockHandler.mock.calls[0][0].detail).toEqual({
nombre: 'Ana Gómez',
email: 'ana@example.com'
});
});
});
Mocking y Pruebas de Componentes Asíncronos 🌐
En aplicaciones reales, tus componentes a menudo necesitarán consumir APIs externas. Vamos a ver cómo probar un componente que realiza una petición HTTP utilizando fetch simulado con Vitest mocks.
El Componente: src/components/ListaUsuarios.svelte
<script>
import { onMount } from 'svelte';
let usuarios = [];
let cargando = true;
let error = null;
onMount(async () => {
try {
const res = await fetch('https://jsonplaceholder.typicode.com/users');
if (!res.ok) throw new Error('Error al cargar usuarios');
usuarios = await res.json();
} catch (err) {
error = err.message;
} finally {
cargando = false;
}
});
</script>
<div>
<h2>Lista de Usuarios</h2>
{#if cargando}
<p data-testid="cargando">Cargando...</p>
{:else if error}
<p data-testid="error">{error}</p>
{:else}
<ul>
{#each usuarios as usuario}
<li data-testid="usuario-item">{usuario.name}</li>
{/each}
</ul>
{/if}
</div>
La Prueba: src/components/ListaUsuarios.test.js
import { render, waitFor } from '@testing-library/svelte';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import ListaUsuarios from './ListaUsuarios.svelte';
describe('Componente ListaUsuarios', () => {
beforeEach(() => {
vi.restoreAllMocks();
});
it('muestra el estado de carga inicialmente y luego renderiza los usuarios', async () => {
const usuariosMock = [
{ id: 1, name: 'Carlos Pérez' },
{ id: 2, name: 'María López' }
];
global.fetch = vi.fn().mockResolvedValue({
ok: true,
json: async () => usuariosMock
});
const { getByTestId, getAllByTestId } = render(ListaUsuarios);
// Verificar estado de carga
expect(getByTestId('cargando')).toBeInTheDocument();
// Esperar a que la promesa se resuelva y los elementos aparezcan
await waitFor(() => {
const items = getAllByTestId('usuario-item');
expect(items).toHaveLength(2);
expect(items[0]).toHaveTextContent('Carlos Pérez');
expect(items[1]).toHaveTextContent('María López');
});
});
it('muestra un mensaje de error si la petición falla', async () => {
global.fetch = vi.fn().mockResolvedValue({
ok: false
});
const { getByTestId } = render(ListaUsuarios);
await waitFor(() => {
expect(getByTestId('error')).toHaveTextContent('Error al cargar usuarios');
});
});
});
Buenas Prácticas y Consejos para Testing en Svelte 🌟
Para finalizar, aquí tienes una tabla resumida con buenas prácticas recomendadas para mantener un conjunto de pruebas limpio, rápido y mantenible:
| Práctica | Descripción | Beneficio |
|---|---|---|
Atributos data-testid | Usar identificadores específicos para pruebas en elementos clave. | Evita que las pruebas se rompan al cambiar clases CSS o etiquetas HTML. |
| Pruebas basadas en comportamiento | Simular acciones reales del usuario (click, input). | Garantiza que la aplicación funcione desde la perspectiva del usuario final. |
| Limpieza de mocks | Limpiar los mocks con vi.restoreAllMocks() o beforeEach. | Evita que las pruebas interfieran entre sí compartiendo estado global. |
Uso de waitFor | Esperar de manera asíncrona cambios de estado complejos o llamadas de red. | Previene falsos negativos por condiciones de carrera (race conditions). |
Preguntas Frecuentes (FAQ)
- ¿Puedo usar Jest en lugar de Vitest con Svelte? Sí, es posible, pero requiere una configuración más compleja con
babel-jesty transformaciones especiales para archivos.svelte. Vitest se integra de forma nativa con Vite, lo que lo hace mucho más rápido y sencillo en Svelte. - ¿Es obligatorio usar JSDOM? No necesariamente. También puedes usar
happy-dom, que suele ser más rápido y consume menos memoria en la mayoría de los casos de pruebas unitarias.
Conclusión 🎉
Has aprendido a configurar un entorno completo de pruebas en Svelte utilizando Vitest y Svelte Testing Library. Desde pruebas unitarias básicas de componentes interactivos hasta formularios complejos y llamadas asíncronas con mocks, ahora cuentas con las herramientas necesarias para desarrollar software de alta calidad y garantizar que tus componentes funcionen perfectamente ante cualquier cambio.
Tutoriales relacionados
- SvelteKit y Formularios Dinámicos: Construyendo Interacciones Modernas con Mejora Progresiva 🚀intermediate20 min
- Svelte y la Interoperabilidad con Componentes de Otros Frameworks: Integrando React y Vue 🔄intermediate15 min
- SvelteKit y Adaptadores: Desplegando Tu Aplicación en Cualquier Plataforma 🌐intermediate10 min
- Svelte y el Arte de la Modularización: Creando Componentes Reutilizables y Prácticos ✨intermediate18 min
- Svelte y el WebSockets: Comunicación en Tiempo Real para Aplicaciones Dinámicas ⚡intermediate20 min
Comentarios (0)
Aún no hay comentarios. ¡Sé el primero!