Capítulo 16: Testes Automatizados com Vitest e Vue Test Utils

Especialização em Front-End com Vue • Vue 3 & Nuxt 3 • Composition API, Pinia, Vue Router, SSR e TypeScript


🗺️ Mapa Conceitual da Pirâmide de Testes no Vue 3

flowchart TD
    A["Código Fonte Vue 3 + TypeScript"] --> B["Test Runner: Vitest (Nativo do Vite)"]

    subgraph NIVEIS_TESTE["Níveis de Validação Automatizada"]
        C["1. Testes Unitários de Composables (Lógica Pura)"]
        D["2. Testes de Componentes (@vue/test-utils + Happy-DOM)"]
        E["3. Testes de Integração de Stores (Pinia Testing)"]
        F["4. Testes End-to-End (Playwright / Cypress)"]
    end

    B --> NIVEIS_TESTE

    D --> G["mount() / shallowMount()"]
    G --> H["trigger('click') / setValue()"]
    H --> I["await flushPromises() (Microtasks)"]
    I --> J["expect(wrapper.emitted()).toHaveProperty(...)"]

    style A fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
    style B fill:#fff3e0,stroke:#ff9800,stroke-width:2px
    style NIVEIS_TESTE fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
    style J fill:#e8f5e9,stroke:#4caf50,stroke-width:2px

🏛️ 1. Testes Automatizados de Alta Performance com Vitest

O Vitest é o executor de testes unitários e de integração oficial e recomendado para o ecossistema Vue 3. Por compartilhar exatamente a mesma pipeline de configuração (vite.config.ts), plugins e resolução de caminhos do Vite, o Vitest elimina a necessidade de duplicar configurações complexas de Babel ou ts-jest, executando testes com suporte nativo a ESM e velocidade 10x superior ao Jest tradicional.

1.1 O Papel da Biblioteca @vue/test-utils

A @vue/test-utils provê utilitários oficiais para montar e interagir com componentes Vue em um ambiente simulado de DOM (geralmente happy-dom ou jsdom):

  • mount(Component, options): Monta o componente completo, incluindo todos os seus componentes filhos e subárvores. É a abordagem recomendada para testes de integração de componentes funcionais.
  • shallowMount(Component, options): Monta apenas o componente alvo, substituindo todos os componentes filhos por stubs vazios (<stub-component>). Útil para testes unitários isolados onde filhos realizam chamadas pesadas.

1.2 Simulação de Eventos do Usuário com trigger e setValue

Interações de usuário são assíncronas por natureza no Vue devido à reconciliação em lote do Virtual DOM:

const input = wrapper.find('input[type="text"]');
await input.setValue('Novo Valor'); // setValue atualiza o v-model e despacha eventos

const button = wrapper.find('button');
await button.trigger('click'); // Sempre use await para aguardar a microtarefa do DOM

1.3 flushPromises() e Resolução de Tarefas Assíncronas

Quando um componente dispara Promises (como chamadas a APIs ou timers assíncronos), usar apenas nextTick() pode não ser suficiente, pois nextTick() resolve apenas uma única passagem da fila de microtarefas.

  • O utilitário flushPromises() força a resolução de todas as microtarefas pendentes na fila do JavaScript antes que as asserções (expect) sejam avaliadas.

1.4 Testes de Stores com @pinia/testing

Para testar componentes que dependem de stores Pinia sem instanciar serviços reais de backend, utiliza-se createTestingPinia():

const wrapper = mount(MeuComponente, {
  global: {
    plugins: [createTestingPinia({
      initialState: { auth: { user: { name: 'Admin' } } },
      stubActions: false // Permite que actions reais executem se desejado
    })]
  }
});

⚖️ 2. Tabela Comparativa: Jest vs Vitest

Critério de Avaliação Jest (Legado) Vitest (Moderno)
Pipeline de Compilação Separada; requer Babel ou ts-jest duplicado. Idêntica ao Vite (mesmos aliases e plugins).
Suporte a ESM Nativo Complexo e experimental. Nativo de Primeira Classe.
Velocidade de Execução Moderada a Lenta (sobrecarga de transformações). Ultra-Rápida com cache de Vite e multithreading.
Watch Mode (HMR de Testes) Lento em projetos grandes. Instantâneo (re-executa apenas o teste alterado).
Compatibilidade de API API canônica (describe, test, expect). 100% compatível com a sintaxe do Jest.

⚠️ 3. Armadilhas Comuns em Produção (Gotchas & Anti-Patterns)

Anti-Pattern 1: Esquecer o await ao Chamar trigger() ou setValue()

Erro Comum: Escrever wrapper.find('button').trigger('click'); e imediatamente na linha seguinte fazer expect(wrapper.text()).toContain(...). Consequência: O teste falhará aleatoriamente (flaky test), pois o Vue ainda não concluiu a atualização física do DOM naquele ciclo de clock.

Anti-Pattern 2: Testar Detalhes Internos de Implementação

Erro Comum: Inspecionar o estado interno de variáveis privadas via wrapper.vm.minhaVariavelPrivada. Abordagem Correta: Teste como o usuário enxerga e interage: forneça entradas (props, cliques, digitação) e valide saídas (texto renderizado na tela e eventos emitidos).


💻 4. Código Conceitual Arquitetural Comentado

// tests/unit/CounterCard.spec.ts
import { describe, it, expect } from 'vitest';
import { mount } from '@vue/test-utils';
import CounterCard from '@/components/CounterCard.vue';

describe('Componente CounterCard.vue', () => {
  it('deve renderizar o título e o valor inicial passado por props', () => {
    const wrapper = mount(CounterCard, {
      props: {
        title: 'Métrica de Conexões',
        initialCount: 5
      }
    });

    expect(wrapper.find('h3').text()).toBe('Métrica de Conexões');
    expect(wrapper.find('[data-testid="counter-value"]').text()).toBe('5');
  });

  it('deve emitir o evento "incremented" com o novo valor ao clicar no botão', async () => {
    const wrapper = mount(CounterCard, {
      props: { title: 'Métrica', initialCount: 10 }
    });

    const btn = wrapper.find('[data-testid="btn-add"]');
    await btn.trigger('click');

    // Valida emissão do contrato de evento
    expect(wrapper.emitted('incremented')).toBeTruthy();
    expect(wrapper.emitted('incremented')?.[0]).toEqual([11]);
  });
});

🛠️ 5. Cheat-Sheet de Comandos e Utilitários de Teste

# Execução da suíte no terminal
npx vitest run          # Executa uma única vez (CI/CD)
npx vitest              # Modo interativo com watch instantâneo
npx vitest --coverage   # Gera relatório de cobertura de código
// Asserções frequentes com @vue/test-utils
expect(wrapper.find('.modal').exists()).toBe(false);
expect(wrapper.classes()).toContain('active');
expect(wrapper.attributes('disabled')).toBeDefined();
expect(wrapper.emitted('submit')).toHaveLength(1);