12k
All articles

Testando Componentes Svelte 5 com Vitest

Teste componentes Svelte 5 com Vitest usando mount, testing-library ou browser mode, além da configuração certa para runes, efeitos e snippets.

OpenReplay Team
OpenReplay Team
Testando Componentes Svelte 5 com Vitest

O Svelte 5 mudou a forma como os testes de componentes são configurados: não é mais possível instanciar um componente com new Component({ target }) — essa API de construtor foi removida. Em vez disso, monte componentes com mount() do svelte, render() do @testing-library/svelte, ou render() do vitest-browser-svelte. As runes ($state, $derived, $effect, $props) só são executadas após o compilador do Svelte processar o arquivo, portanto a configuração de testes deve direcionar os arquivos de teste por esse compilador. Este guia aborda a configuração atual e correta do Vitest, além dos padrões específicos para testar runes e componentes no Svelte 5.

Principais Conclusões

  • No Svelte 5, o construtor new Component({ target }) e os métodos $set/$on/$destroy foram removidos; monte componentes com mount() do svelte ou um helper render(), e acesse props via $props().
  • Para testar runes diretamente, coloque-as em um arquivo cujo nome inclua .svelte (por exemplo, counter.svelte.test.ts) para que o compilador processe as runes antes que o Vitest execute as asserções.
  • Os effects não são executados de forma síncrona — envolva o código que usa $effect em $effect.root() e chame flushSync() para processar os effects pendentes antes de realizar as asserções.
  • Para @testing-library/svelte no Svelte 5, adicione o plugin svelteTesting de @testing-library/svelte/vite; ele define a condição de resolução do browser e limpa o DOM automaticamente após cada teste.
  • O vitest-browser-svelte executa seus componentes em um browser real via Playwright e requer o Vitest 4; sempre use await render(...), consulte elementos com locators e faça asserções com await expect.element(...).

O que mudou nos testes do Svelte 5?

A maioria dos tutoriais de testes do Svelte disponíveis online é da era do Svelte 4 e utiliza APIs que não existem mais. Se um guia instancia um componente com new, chama component.$set ou lê $$props, ele está desatualizado. Veja o mapa de migração:

Svelte 4 (removido)Svelte 5 (atual)
new Component({ target })mount(Component, { target }) ou render(Component)
component.$set(props)passe props para render / rerender
component.$on / component.$destroyprops de callback / unmount(component)
$$props$props()
Prioridade ao fireEventuserEvent ou locators no modo browser
Configuração com svelte-jesterPlugin svelteTesting (Vitest)

Existem duas configurações atuais, ambas válidas. A primeira é o @testing-library/svelte rodando no jsdom — de alto nível, familiar, e com suporte às versões 3, 4 e 5 do Svelte. A segunda é o vitest-browser-svelte, que renderiza componentes em um browser real via Playwright usando o Browser Mode estável do Vitest. O Browser Mode deixou de ser experimental no Vitest 4, portanto ignore qualquer tutorial que ainda o chame de experimental.

Como configurar o Vitest para testes com Svelte?

Toda configuração de testes no Svelte 5 compartilha um requisito: o Vitest deve resolver os entry points de browser dos seus pacotes, mesmo sendo executado no Node. A documentação do Svelte faz isso com resolve.conditions. Comece com uma configuração base e expanda a partir dela.

Para testes de componentes no jsdom, instale o jsdom e adicione o ambiente e o 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'
  }
});

O plugin svelteTesting define a condição de resolução do browser automaticamente e, no Vitest, configura e limpa o DOM antes e depois de cada teste — portanto, não é necessário escrever afterEach(cleanup) manualmente. Não defina resolve.conditions manualmente; o plugin já cuida disso.

Para testes em browser real, utilize o Browser Mode do Vitest. A partir do Vitest 4, os pacotes de provider são instalados separadamente, e a configuração importa playwright() de @vitest/browser-playwright com um array instances — a forma antiga com provider: 'playwright', name: 'chromium' está depreciada:

// 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' }]
    }
  }
});

Escrevendo um teste de componente

Um componente Svelte 5 lê suas entradas com $props() e mantém o estado local em $state. Veja o componente que ambas as abordagens irão testar:

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

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

Com @testing-library/svelte, chame render, consulte por role, interaja com userEvent e use await no clique:

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('incrementa ao clicar', 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');
});

O mount()/unmount() do próprio Svelte é a API de baixo nível por trás desses helpers. A documentação observa que a abordagem com mount() puro é “de baixo nível e um tanto frágil” por fazer asserções contra o innerHTML exato, portanto prefira um helper de render para testes de componentes.

Com vitest-browser-svelte, sempre use await render(...), consulte com locators e faça asserções com expect.element, que tenta novamente automaticamente até que a asserção seja aprovada:

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

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

Testando runes e lógica reativa

Antes de montar qualquer coisa, pergunte-se se você realmente precisa de um teste de componente. A documentação do Svelte recomenda extrair a lógica reativa para um módulo .svelte.js e testá-la de forma isolada, sem o overhead de um componente. Esse módulo pode usar runes porque seu nome de arquivo inclui .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++; }
  };
}

Teste-o diretamente — o arquivo de teste também deve incluir .svelte em seu nome, por exemplo counter.svelte.test.js, para que o compilador processe as runes:

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

test('deriva o dobro a partir de count', () => {
  const counter = createCounter(2);
  expect(counter.doubled).toBe(4);
  counter.increment();
  expect(counter.doubled).toBe(6);
});

Os effects são a exceção: eles não são executados de forma síncrona. Quando o código em teste usa $effect, envolva-o em $effect.root() e chame flushSync() para executar os effects pendentes antes de fazer as asserções, exatamente como a documentação de testes do Svelte demonstra:

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

test('registra cada atualização', () => {
  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();
});

Testando snippets e props

Snippets são o substituto do Svelte 5 para slots, renderizados com {@render} e recebidos via $props(). Para um componente que renderiza um snippet children, o teste mais simples é um pequeno componente wrapper com um data-testid, que você então consulta. Para snippets cujos argumentos você deseja inspecionar, a documentação do vitest-browser-svelte utiliza a API createRawSnippet do Svelte para passar um snippet diretamente e verificar o que ele recebeu:

<!-- 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('passa a saudação para o 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. browser mode: qual escolher?

Escolha jsdom + @testing-library/svelte para testes rápidos e sem browser de markup e lógica; escolha vitest-browser-svelte quando precisar de APIs reais do browser — layout, foco, IntersectionObserver — sem precisar simulá-las.

jsdom + testing-libraryvitest-browser-svelte
AmbienteDOM simulado (jsdom)Browser real via Playwright
Velocidade / configuraçãoRápido, sem download de browserMais pesado por teste; requer um browser
APIs do browserSimuladas / mockadasNativas, sem mocking
Sincronização de effectsFrequentemente requer flushSyncLocators tentam novamente automaticamente; raramente necessário
RequisitosSuporte ao Svelte 3/4/5Vitest 4

Como os locators no modo browser e o expect.element tentam novamente até que a asserção seja aprovada, raramente é necessário recorrer ao flushSync em testes de componentes — embora alguns casos extremos ainda precisem dele. Mantenha a lógica reativa pura em arquivos .svelte.test com jsdom para maior velocidade, e reserve o modo browser para comportamentos que dependem de um motor de renderização real.

Comece extraindo a lógica para módulos .svelte.js e testando-a de forma isolada, adicione testes de componentes com jsdom por meio do plugin svelteTesting, e recorra ao vitest-browser-svelte quando um teste genuinamente precisar de um browser real. Configure uma vez, fixe nas APIs atuais descritas acima, e sua suite de testes no Svelte 5 se manterá livre dos padrões removidos do Svelte 4 que quebram a maioria dos tutoriais mais antigos.

Perguntas Frequentes

Por que meu $effect não está sendo executado em um teste do Vitest?

Os effects não são executados de forma síncrona em testes, portanto uma asserção colocada logo após uma mudança de estado verá valores desatualizados. Envolva o código que usa o effect em $effect.root() e chame flushSync() do svelte para processar os effects pendentes antes de fazer as asserções. Chame a função de cleanup retornada por $effect.root() quando o teste terminar. Em testes no modo browser, raramente é necessário fazer isso, pois os locators e o expect.element tentam novamente automaticamente, embora alguns casos extremos ainda exijam flushSync.

Ainda preciso do svelte-jester para testar componentes Svelte 5?

Não — o svelte-jester é a solução exclusiva para Jest e é desnecessário com o Vitest. Para o Vitest, adicione o plugin svelteTesting de @testing-library/svelte/vite, que define a condição de resolução do browser e limpa o DOM automaticamente após cada teste. O svelte-jester ainda aparece na documentação de configuração do testing-library como alternativa para o Jest, mas se você estiver usando o Vitest, pode ignorá-lo. Muitos tutoriais mais antigos copiam a configuração do Jest, o que causa falhas desnecessárias na configuração.

Posso testar runes do Svelte 5 em um arquivo .test.js comum?

Não. As runes só são executadas após o compilador do Svelte processar o arquivo, e o compilador só processa arquivos cujo nome inclua .svelte. Para testar runes diretamente, nomeie o arquivo com .svelte, por exemplo counter.svelte.test.js, para que o compilador transforme as runes antes que o Vitest execute as asserções. A mesma regra se aplica a módulos simples que usam runes: nomeie-os com .svelte, como counter.svelte.js, e importe-os normalmente nos seus testes.

Qual versão do Vitest o vitest-browser-svelte requer?

O vitest-browser-svelte requer o Vitest 4.0.0 ou superior; instalá-lo com o Vitest 3 ou anterior resultará em falha. O Browser Mode tornou-se estável no Vitest 4, que também moveu os pacotes de provider para instalações separadas — você importa playwright() de @vitest/browser-playwright e configura um array instances. A forma antiga com provider: 'playwright', name: 'chromium' do Vitest 2 está depreciada e não é mais correta. O pacote está disponível na organização vitest-community no 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.