12k
All articles

Pruebas de Componentes Svelte 5 con Vitest

Prueba componentes de Svelte 5 con Vitest usando mount, testing-library o browser mode, más la configuración correcta para runes, efectos y snippets.

OpenReplay Team
OpenReplay Team
Pruebas de Componentes Svelte 5 con Vitest

Svelte 5 cambió la forma de configurar las pruebas de componentes: ya no se instancia un componente con new Component({ target }) — esa API de constructor fue eliminada. En su lugar, los componentes se montan con mount() de svelte, render() de @testing-library/svelte, o render() de vitest-browser-svelte. Las runes ($state, $derived, $effect, $props) solo se ejecutan una vez que el compilador de Svelte ha procesado el archivo, por lo que la configuración de pruebas debe enrutar los archivos de prueba a través de dicho compilador. Esta guía cubre la configuración actual y correcta de Vitest, así como los patrones específicos para probar runes y componentes en Svelte 5.

Puntos Clave

  • En Svelte 5, el constructor new Component({ target }) y los métodos $set/$on/$destroy fueron eliminados; los componentes se montan con mount() de svelte o un helper render(), y las props se leen mediante $props().
  • Para probar runes directamente, colócalas en un archivo cuyo nombre incluya .svelte (por ejemplo, counter.svelte.test.ts) para que el compilador procese las runes antes de que Vitest ejecute las aserciones.
  • Los efectos no se ejecutan de forma síncrona — envuelve el código que usa $effect en $effect.root() y llama a flushSync() para vaciar los efectos pendientes antes de realizar las aserciones.
  • Para usar @testing-library/svelte con Svelte 5, añade el plugin svelteTesting de @testing-library/svelte/vite; este establece la condición de resolución del navegador y limpia el DOM automáticamente tras cada prueba.
  • vitest-browser-svelte ejecuta el componente en un navegador real mediante Playwright y requiere Vitest 4; siempre usa await render(...), consulta con locators y realiza aserciones con await expect.element(...).

¿Qué cambió en las pruebas de Svelte 5?

La mayoría de los tutoriales de pruebas de Svelte disponibles en línea son de la era de Svelte 4 y utilizan APIs que ya no existen. Si una guía instancia un componente con new, llama a component.$set o lee $$props, está desactualizada. A continuación se presenta el mapa de migración:

Svelte 4 (eliminado)Svelte 5 (actual)
new Component({ target })mount(Component, { target }) o render(Component)
component.$set(props)pasar props a render / rerender
component.$on / component.$destroyprops de callback / unmount(component)
$$props$props()
Prioridad a fireEventuserEvent o locators en modo navegador
Configuración con svelte-jesterPlugin svelteTesting (Vitest)

Actualmente existen dos configuraciones válidas. La primera es @testing-library/svelte ejecutándose sobre jsdom — de alto nivel, familiar y compatible con las versiones 3, 4 y 5 de Svelte. La segunda es vitest-browser-svelte, que renderiza componentes en un navegador real mediante Playwright usando el Browser Mode estable de Vitest. El Browser Mode dejó de ser experimental en Vitest 4, por lo que conviene ignorar cualquier tutorial que aún lo describa como tal.

¿Cómo se configura Vitest para pruebas de Svelte?

Toda configuración de pruebas en Svelte 5 comparte un requisito: Vitest debe resolver los puntos de entrada browser de los paquetes aunque se ejecute en Node. La documentación de Svelte lo gestiona mediante resolve.conditions. Comienza con una configuración base y amplíala según sea necesario.

Para pruebas de componentes sobre jsdom, instala jsdom y añade el entorno junto con el plugin svelteTesting:

// vite.config.js
import { defineConfig } from 'vitest/config';
import { svelte } from '@sveltejs/vite-plugin-svelte';
import { svelteTesting } from '@testing-library/svelte/vite';

export default defineConfig({
  plugins: [svelte(), svelteTesting()],
  test: {
    environment: 'jsdom'
  }
});

El plugin svelteTesting establece la condición de resolución del navegador y, en Vitest, configura y limpia automáticamente el DOM antes y después de cada prueba — por lo que no es necesario escribir afterEach(cleanup) manualmente. No añadas resolve.conditions de forma manual; el plugin ya se encarga de ello.

Para pruebas en un navegador real, utiliza el Browser Mode de Vitest. A partir de Vitest 4, los paquetes de proveedores se instalan por separado, y la configuración importa playwright() desde @vitest/browser-playwright con un array instances — la forma antigua provider: 'playwright', name: 'chromium' está obsoleta:

// vite.config.js
import { defineConfig } from 'vitest/config';
import { svelte } from '@sveltejs/vite-plugin-svelte';
import { playwright } from '@vitest/browser-playwright';

export default defineConfig({
  plugins: [svelte()],
  test: {
    browser: {
      enabled: true,
      provider: playwright(),
      instances: [{ browser: 'chromium' }]
    }
  }
});

Escribir una prueba de componente

Un componente de Svelte 5 lee sus entradas con $props() y mantiene el estado local en $state. A continuación se muestra el componente que ambos enfoques utilizarán para las pruebas:

<!-- Counter.svelte -->
<script>
  let { initial = 0 } = $props();
  let count = $state(initial);
</script>

<button onclick={() => count++}>{count}</button>

Con @testing-library/svelte, llama a render, consulta por rol, simula la interacción con userEvent y usa await en el clic:

import { render, screen } from '@testing-library/svelte';
import userEvent from '@testing-library/user-event';
import { expect, test } from 'vitest';
import Counter from './Counter.svelte';

test('increments on click', async () => {
  const user = userEvent.setup();
  render(Counter, { initial: 0 });
  const button = screen.getByRole('button');
  expect(button).toHaveTextContent('0');
  await user.click(button);
  expect(button).toHaveTextContent('1');
});

El propio mount()/unmount() de Svelte es la API de bajo nivel que subyace a estos helpers. La documentación señala que el enfoque con mount() directamente es “de bajo nivel y algo frágil” porque realiza aserciones contra el innerHTML exacto, por lo que se recomienda usar un helper de renderizado para las pruebas de componentes.

Con vitest-browser-svelte, siempre usa await render(...), consulta con locators y realiza aserciones con expect.element, que reintenta automáticamente hasta que la aserción se cumple:

import { render } from 'vitest-browser-svelte';
import { expect, test } from 'vitest';
import Counter from './Counter.svelte';

test('increments on click', async () => {
  const screen = await render(Counter, { initial: 0 });
  const button = screen.getByRole('button');
  await button.click();
  await expect.element(button).toHaveTextContent('1');
});

Probar runes y lógica reactiva

Antes de montar cualquier componente, conviene preguntarse si realmente se necesita una prueba de componente. La documentación de Svelte recomienda extraer la lógica reactiva a un módulo .svelte.js y probarlo de forma aislada, sin la sobrecarga de un componente. Ese módulo puede usar runes porque su nombre de archivo incluye .svelte:

// counter.svelte.js
export function createCounter(initial = 0) {
  let count = $state(initial);
  const doubled = $derived(count * 2);
  return {
    get count() { return count; },
    get doubled() { return doubled; },
    increment() { count++; }
  };
}

Pruébalo directamente — el archivo de prueba también debe incluir .svelte en su nombre, por ejemplo counter.svelte.test.js, para que el compilador procese las runes:

import { expect, test } from 'vitest';
import { createCounter } from './counter.svelte.js';

test('derives doubled from count', () => {
  const counter = createCounter(2);
  expect(counter.doubled).toBe(4);
  counter.increment();
  expect(counter.doubled).toBe(6);
});

Los efectos son la excepción: no se ejecutan de forma síncrona. Cuando el código bajo prueba usa $effect, envuélvelo en $effect.root() y llama a flushSync() para ejecutar los efectos pendientes antes de realizar las aserciones, tal como muestra la documentación de pruebas de Svelte:

import { flushSync } from 'svelte';
import { expect, test } from 'vitest';
import { logger } from './logger.svelte.js';

test('logs each update', () => {
  const cleanup = $effect.root(() => {
    let count = $state(0);
    const log = logger(() => count);
    flushSync();
    expect(log).toEqual([0]);
    count = 1;
    flushSync();
    expect(log).toEqual([0, 1]);
  });
  cleanup();
});

Probar snippets y props

Los snippets son el reemplazo de los slots en Svelte 5, se renderizan con {@render} y se reciben a través de $props(). Para un componente que renderiza un snippet children, la prueba más sencilla consiste en usar un pequeño componente envolvente con un data-testid y luego consultarlo. Para snippets cuyos argumentos se deseen inspeccionar, la documentación de vitest-browser-svelte utiliza la API createRawSnippet de Svelte para pasar un snippet directamente y verificar lo que recibió:

<!-- Greeting.svelte -->
<script>
  let { name, message } = $props();
  const greeting = $derived(`Hello, ${name}!`);
</script>

<p>{@render message?.(greeting)}</p>
import { render } from 'vitest-browser-svelte';
import { createRawSnippet } from 'svelte';
import { expect, test } from 'vitest';
import Greeting from './Greeting.svelte';

test('passes the greeting into the snippet', async () => {
  const screen = await render(Greeting, {
    name: 'Alice',
    message: createRawSnippet((greeting) => ({
      render: () => `<span data-testid="message">${greeting()}</span>`
    }))
  });
  await expect.element(screen.getByTestId('message'))
    .toHaveTextContent('Hello, Alice!');
});

jsdom vs. modo navegador: ¿cuál elegir?

Elige jsdom + @testing-library/svelte para pruebas rápidas sin navegador sobre marcado y lógica; elige vitest-browser-svelte cuando necesites APIs reales del navegador — layout, foco, IntersectionObserver — sin necesidad de simularlas con mocks.

jsdom + testing-libraryvitest-browser-svelte
EntornoDOM simulado (jsdom)Navegador real mediante Playwright
Velocidad / configuraciónRápido, sin descarga de navegadorMayor peso por prueba; requiere un navegador
APIs del navegadorSimuladas / mockeadasNativas, sin mocking
Vaciado síncronoA menudo requiere flushSyncLos locators reintentan automáticamente; raramente necesario
RequisitosSoporte para Svelte 3/4/5Vitest 4

Dado que los locators en modo navegador y expect.element reintentan hasta que la aserción se cumple, raramente se necesita flushSync en las pruebas de componentes — aunque algunos casos límite aún lo requieren. Mantén la lógica reactiva pura en archivos .svelte.test con jsdom para mayor velocidad, y reserva el modo navegador para comportamientos que dependan de un motor de renderizado real.

Comienza extrayendo la lógica a módulos .svelte.js y probándola de forma aislada, añade pruebas de componentes con jsdom mediante el plugin svelteTesting, y recurre a vitest-browser-svelte cuando una prueba genuinamente necesite un navegador real. Configura el entorno una sola vez, fíjate en las APIs actuales descritas anteriormente, y tu suite de Svelte 5 se mantendrá libre de los patrones eliminados de Svelte 4 que rompen la mayoría de los tutoriales más antiguos.

Preguntas Frecuentes

¿Por qué mi $effect no se ejecuta en una prueba de Vitest?

Los efectos no se ejecutan de forma síncrona en las pruebas, por lo que una aserción colocada justo después de un cambio de estado verá valores desactualizados. Envuelve el código que usa efectos en $effect.root() y llama a flushSync() desde svelte para vaciar los efectos pendientes antes de realizar las aserciones. Llama a la función de limpieza devuelta por $effect.root() cuando la prueba finalice. En pruebas en modo navegador raramente necesitarás esto, ya que los locators y expect.element reintentan automáticamente, aunque algunos casos límite aún requieren flushSync.

¿Sigo necesitando svelte-jester para probar componentes de Svelte 5?

No — svelte-jester es la solución exclusiva para Jest y no es necesaria con Vitest. Para Vitest, añade el plugin svelteTesting de @testing-library/svelte/vite, que establece la condición de resolución del navegador y limpia el DOM automáticamente tras cada prueba. svelte-jester sigue apareciendo en la documentación de testing-library como alternativa para Jest, pero si usas Vitest debes ignorarlo. Muchos tutoriales más antiguos copian la configuración de Jest, lo que provoca fallos innecesarios en la configuración.

¿Puedo probar runes de Svelte 5 en un archivo .test.js normal?

No. Las runes solo se ejecutan después de que el compilador de Svelte procese el archivo, y el compilador únicamente procesa archivos cuyo nombre incluya .svelte. Para probar runes directamente, nombra el archivo incluyendo .svelte, por ejemplo counter.svelte.test.js, para que el compilador transforme las runes antes de que Vitest ejecute las aserciones. La misma regla aplica a los módulos simples que usan runes: nómbralos con .svelte, como counter.svelte.js, e impórtalos normalmente en tus pruebas.

¿Qué versión de Vitest requiere vitest-browser-svelte?

vitest-browser-svelte requiere Vitest 4.0.0 o superior; instalarlo con Vitest 3 o anterior fallará. El Browser Mode se volvió estable en Vitest 4, que también trasladó los paquetes de proveedores a instalaciones separadas — se importa playwright() desde @vitest/browser-playwright y se configura un array instances. La forma antigua provider: 'playwright', name: 'chromium' de Vitest 2 está obsoleta y ya no es correcta. El paquete se encuentra bajo la organización vitest-community en GitHub.

DevTools for the frontend

Gain Debugging Superpowers

Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.