Testes em Vue 3 em 2026: Vitest, Vue Test Utils e Perguntas de Entrevista

Um guia prático de testes em Vue para 2026: configurar o Vitest, montar componentes com o Vue Test Utils, testar composables e stores do Pinia, mockar APIs, medir cobertura e as perguntas de entrevista que as equipes de contratação realmente fazem.

Fluxo de testes em Vue com Vitest e Vue Test Utils em 2026

Testar em Vue é o que separa uma refatoração confiante de um chute frágil, e em 2026 o ferramental se consolidou em torno de duas bibliotecas: Vitest como executor de testes e Vue Test Utils para montar componentes. Este guia percorre a configuração da stack, os testes de componentes e composables, o mock de dependências e as respostas para as perguntas de entrevista sobre testes em Vue que as equipes de contratação de fato fazem.

A stack moderna de testes em Vue em 2026

A recomendação padrão da documentação do Vue é usar o Vitest para testes unitários e de componente, o Vue Test Utils como API de montagem de baixo nível e o Playwright ou o Cypress para cobertura ponta a ponta. O Vitest reaproveita a mesma configuração do Vite usada pela aplicação, então os testes rodam pelo mesmo pipeline de transformação, com hot reload praticamente instantâneo.

Configurando o Vitest para um projeto Vue 3

O Vitest compartilha o pipeline do Vite, o que significa que um único arquivo de configuração comanda tanto o servidor de desenvolvimento quanto o executor de testes. A opção environment precisa ser definida como jsdom ou happy-dom para que os testes de componente tenham um DOM onde renderizar. A flag globals: true expõe describe, it e expect sem que seja preciso importá-los em cada arquivo.

vitest.config.tstypescript
import { defineConfig } from 'vitest/config'
import vue from '@vitejs/plugin-vue'
import { fileURLToPath } from 'node:url'

export default defineConfig({
  plugins: [vue()],
  test: {
    // jsdom provides document/window so components can mount
    environment: 'jsdom',
    // expose describe/it/expect globally
    globals: true,
    // run this before each test file (matchers, cleanup)
    setupFiles: ['./tests/setup.ts'],
    // only pick up *.spec.ts and *.test.ts files
    include: ['**/*.{test,spec}.ts'],
  },
  resolve: {
    alias: {
      // mirror the @ alias used across the app
      '@': fileURLToPath(new URL('./src', import.meta.url)),
    },
  },
})

Com o Vitest 3, a configuração acima já funciona de imediato após instalar vitest, @vue/test-utils, @vitejs/plugin-vue e jsdom. Como o alias espelha a configuração da aplicação, imports como @/composables/useCart são resolvidos de forma idêntica nos testes e em produção.

Montando componentes com o Vue Test Utils

O Vue Test Utils oferece dois pontos de entrada: mount, que renderiza a árvore completa de componentes, e shallowMount, que substitui os componentes filhos por stubs. Para a maioria dos testes unitários, o mount é preferível porque exercita o comportamento real de renderização. O wrapper retornado fornece utilitários de consulta como find, get e text para verificar a saída renderizada.

PriceTag.spec.tstypescript
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import PriceTag from '@/components/PriceTag.vue'

describe('PriceTag', () => {
  it('formats the amount as currency', () => {
    const wrapper = mount(PriceTag, {
      // props are passed through the mounting options
      props: { amount: 4200, currency: 'EUR' },
    })

    // get() throws if the selector is missing, unlike find()
    expect(wrapper.get('[data-test="price"]').text()).toBe('€42.00')
  })

  it('applies a discount class when on sale', () => {
    const wrapper = mount(PriceTag, {
      props: { amount: 4200, currency: 'EUR', onSale: true },
    })

    // classes() returns the array of applied CSS classes
    expect(wrapper.get('[data-test="price"]').classes()).toContain('price--sale')
  })
})

Mirar nos elementos por atributos data-test em vez de classes CSS mantém os testes resilientes: uma mudança de estilo em .price--sale não quebra o seletor, apenas uma mudança real de comportamento quebraria. Esse desacoplamento é um tema recorrente em testes de Vue que se mantêm ao longo do tempo.

Testando eventos de usuário e eventos emitidos

Componentes interativos exigem verificações sobre o que acontece após um clique ou uma digitação. O Vue Test Utils retorna uma promise a partir do trigger, e aguardá-la esvazia a fila de reatividade do Vue, de modo que o DOM reflita a atualização. O utilitário emitted registra cada evento personalizado que o componente disparou, e é assim que os contratos entre pai e filho são verificados.

SearchBar.spec.tstypescript
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import SearchBar from '@/components/SearchBar.vue'

describe('SearchBar', () => {
  it('emits search with the typed query', async () => {
    const wrapper = mount(SearchBar)

    // setValue writes to the input and fires an input event
    await wrapper.get('input').setValue('vitest')
    // awaiting trigger flushes the reactivity queue before assertions
    await wrapper.get('[data-test="submit"]').trigger('click')

    // emitted() returns a record of every event and its payloads
    const events = wrapper.emitted('search')
    expect(events).toHaveLength(1)
    // events[0] is the first emission, [0] its first argument
    expect(events![0][0]).toBe('vitest')
  })
})
Sempre aguarde as atualizações do DOM

O Vue agrupa as atualizações reativas de forma assíncrona. Esquecer de usar await em uma chamada de trigger ou setValue é a causa mais comum de testes instáveis em Vue: as verificações rodam antes de o DOM re-renderizar, então leem uma saída desatualizada. Quando a mudança de estado acontece fora de um utilitário de evento, importe o nextTick de vue e use await nextTick() antes de verificar.

Testando composables do Vue de forma isolada

Composables que usam apenas APIs de reatividade (ref, computed, watch) não precisam de nenhum componente: são funções puras que retornam estado reativo, então chamá-las diretamente e verificar o .value já basta. Essa é a maneira mais rápida e mais focada de cobrir a lógica de negócio, e combina bem com os padrões apresentados em composables avançados no Vue 3.

useCart.spec.tstypescript
import { describe, it, expect } from 'vitest'
import { useCart } from '@/composables/useCart'

describe('useCart', () => {
  it('tracks total price as items change', () => {
    // composables using only ref/computed run without mounting
    const { items, total, addItem } = useCart()

    expect(total.value).toBe(0)

    addItem({ id: 1, price: 1500, qty: 2 })
    // computed total recalculates synchronously on state change
    expect(total.value).toBe(3000)

    addItem({ id: 2, price: 500, qty: 1 })
    expect(items.value).toHaveLength(2)
    expect(total.value).toBe(3500)
  })
})

Composables que dependem de hooks de ciclo de vida como onMounted são a exceção: eles precisam rodar dentro de uma instância de componente. A solução comum é um pequeno componente auxiliar que chama o composable no setup e expõe o resultado, e então montar esse auxiliar com o Vue Test Utils.

Pronto para mandar bem nas entrevistas de Vue.js / Nuxt.js?

Pratique com nossos simuladores interativos, flashcards e testes tecnicos.

Mockando chamadas de API com vi.mock

Requisições de rede reais deixam os testes lentos e não determinísticos. O Vitest substitui módulos com vi.mock e cria funções espiãs com vi.fn. O padrão abaixo mocka o módulo que encapsula o fetch, de modo que o composable sob teste receba dados controlados sem jamais tocar em um servidor.

useProducts.spec.tstypescript
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { useProducts } from '@/composables/useProducts'
import { getProducts } from '@/api/products'

// replace the whole module with mocked exports
vi.mock('@/api/products', () => ({
  getProducts: vi.fn(),
}))

describe('useProducts', () => {
  beforeEach(() => {
    // reset call history between tests to avoid leakage
    vi.mocked(getProducts).mockReset()
  })

  it('exposes fetched products and clears loading', async () => {
    // define what the mocked API returns for this test
    vi.mocked(getProducts).mockResolvedValue([
      { id: 1, name: 'Keyboard' },
    ])

    const { products, loading, load } = useProducts()
    await load()

    expect(getProducts).toHaveBeenCalledOnce()
    expect(loading.value).toBe(false)
    expect(products.value[0].name).toBe('Keyboard')
  })
})

Mockar na fronteira do módulo mantém a lógica interna do composable intacta e, ao mesmo tempo, dá a cada teste controle total sobre a resposta da API, inclusive os caminhos de erro. Usar mockRejectedValue no lugar permite que um único teste verifique se as falhas definem um estado de erro e param o spinner.

Testando stores do Pinia em 2026

Stores concentram estado compartilhado entre componentes, e o pacote oficial @pinia/testing torna simples testá-los. O createTestingPinia transforma cada action em stub por padrão, então um teste de componente pode verificar que uma action foi despachada sem executar seus efeitos colaterais. O guia de testes do Pinia recomenda essa abordagem para testes no nível de componente.

CheckoutButton.spec.tstypescript
import { describe, it, expect, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import { createTestingPinia } from '@pinia/testing'
import CheckoutButton from '@/components/CheckoutButton.vue'
import { useCartStore } from '@/stores/cart'

describe('CheckoutButton', () => {
  it('dispatches checkout on click', async () => {
    const wrapper = mount(CheckoutButton, {
      global: {
        plugins: [
          // vi.fn spies on actions instead of executing them
          createTestingPinia({ createSpy: vi.fn }),
        ],
      },
    })

    const store = useCartStore()
    await wrapper.get('[data-test="checkout"]').trigger('click')

    // the action is stubbed, so only the dispatch is asserted
    expect(store.checkout).toHaveBeenCalledOnce()
  })
})

Para testar a lógica da própria store em vez da integração com o componente, passe stubActions: false para que as actions executem normalmente e depois verifique o estado resultante. Essa separação entre testes de componente com actions em stub e testes de store com actions reais mantém cada suíte focada em uma única responsabilidade.

Rodando testes em modo watch e medindo cobertura

O Vitest traz um modo watch que reexecuta apenas os testes afetados por um arquivo salvo, o que encurta o ciclo de feedback durante o desenvolvimento para uma fração de segundo. O relatório de cobertura já vem embutido por meio do provider @vitest/coverage-v8 e se integra de forma limpa a um pipeline de integração contínua, onde um teste que falha ou uma regressão de cobertura pode bloquear um merge.

vitest.config.ts (coverage section)typescript
export default defineConfig({
  test: {
    coverage: {
      // v8 is the fastest provider and needs no instrumentation step
      provider: 'v8',
      reporter: ['text', 'html', 'lcov'],
      // fail CI when a threshold drops below the target
      thresholds: {
        statements: 80,
        branches: 75,
        functions: 80,
      },
      // exclude generated and config files from the report
      exclude: ['**/*.config.ts', '**/types/**'],
    },
  },
})

Rodar vitest sozinho inicia o modo watch localmente, enquanto vitest run --coverage executa uma única passagem, adequada para a CI. Definir limiares explícitos transforma a cobertura de uma métrica de vaidade em um portão de qualidade obrigatório: um pull request que derruba a cobertura de branches abaixo de 75 por cento falha automaticamente, o que incentiva os colaboradores a testar os caminhos que adicionam, e não apenas o caminho feliz.

Perguntas comuns de entrevista sobre testes em Vue e suas respostas

Perguntas sobre testes aparecem na maioria das entrevistas de Vue sênior porque revelam como a pessoa candidata pensa em manutenibilidade. As respostas a seguir cobrem os conceitos que surgem com mais frequência, e o conjunto completo é treinado no módulo de entrevista sobre testes em Vue.

Quando usar shallowMount em vez de mount? Recorra ao shallowMount quando um componente renderiza filhos pesados ou já testados e o teste só se importa com a lógica do próprio pai. Transformar os filhos em stubs isola a unidade e acelera a renderização, ao custo de não verificar a integração real entre os componentes.

Como as atualizações assíncronas são tratadas no Vue Test Utils? O Vue aplica as mudanças reativas no próximo tick. Utilitários de evento como trigger e setValue retornam promises que resolvem depois que o DOM é atualizado, então aguardá-los já é suficiente. Para estado alterado fora desses utilitários, await nextTick() força a fila a esvaziar antes de as verificações rodarem.

Qual é a diferença entre find e get? O find retorna um wrapper vazio quando nenhum elemento corresponde e nunca lança erro, o que combina com verificações como expect(wrapper.find('.error').exists()).toBe(false). O get lança um erro imediatamente em um elemento ausente, tornando-o a melhor escolha quando o elemento deveria existir e sua ausência é uma falha genuína.

Por que preferir atributos data-test a seletores de classe? Nomes de classe mudam junto com o estilo e a estrutura, então testes que os consultam quebram em edições cosméticas. Um gancho data-test dedicado expressa a intenção do teste de forma explícita e sobrevive às refatorações, reduzindo os falsos negativos.

Como testar um composable que depende de onMounted? Hooks de ciclo de vida só disparam dentro de uma instância de componente, então o composable não pode ser chamado diretamente. A técnica padrão é um componente descartável que atua como "harness", invocando o composable no setup e expondo o valor de retorno, e então montá-lo com o Vue Test Utils e verificar por meio do wrapper. Isso mantém a fiação de reatividade e de ciclo de vida intacta e ainda assim isola a lógica sob teste. Mais perguntas conceituais e de código estão reunidas no guia de perguntas essenciais de entrevista sobre Vue.js.

Conclusão

Uma configuração de testes em Vue confiável em 2026 se resume a algumas escolhas deliberadas:

  • Rode o Vitest com environment: 'jsdom' e o plugin do Vue para que os testes compartilhem o pipeline exato de transformação do Vite da aplicação.
  • Prefira mount a shallowMount, a menos que um componente filho seja pesado ou já esteja coberto em outro lugar.
  • Sempre use await em trigger, setValue e nextTick antes de verificar, para eliminar testes instáveis dependentes de temporização.
  • Teste composables que usam apenas APIs de reatividade chamando-os diretamente e verificando o .value.
  • Mocke na fronteira do módulo com vi.mock para que o comportamento de rede seja determinístico e os caminhos de erro sejam testáveis.
  • Use createTestingPinia({ createSpy: vi.fn }) para testes de componente e stubActions: false ao exercitar a lógica da store.
  • Consulte os elementos por atributos data-test para que os testes quebrem apenas em mudanças reais de comportamento, nunca por estilo.

Domine esses padrões e testar deixa de ser um fardo para virar uma ferramenta de design, capturando regressões antes que cheguem à produção. Explore a trilha completa de aprendizado de Vue e Nuxt para continuar desenvolvendo habilidades prontas para entrevista.

Comece a praticar!

Teste seus conhecimentos com nossos simuladores de entrevista e testes tecnicos.

Tags

#vue
#testing
#vitest
#vue-test-utils
#interview

Compartilhar

Artigos relacionados