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.

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 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.
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.
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.
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')
})
})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.
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.
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.
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.
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
mountashallowMount, a menos que um componente filho seja pesado ou já esteja coberto em outro lugar. - Sempre use
awaitemtrigger,setValueenextTickantes 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.mockpara 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 estubActions: falseao exercitar a lógica da store. - Consulte os elementos por atributos
data-testpara 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
Compartilhar
Artigos relacionados

Composables Avançados no Vue 3: Padrões Reutilizáveis e Perguntas de Entrevista 2026
Domine composables avançados do Vue 3 com padrões reutilizáveis, tipagem TypeScript e perguntas frequentes de entrevistas técnicas. Guia completo com exemplos práticos de composição, validação de formulários, injeção de dependências e testes unitários.

Vue 3 Pinia vs Vuex: Gerenciamento de Estado Moderno e Perguntas de Entrevista 2026
Comparação detalhada entre Pinia e Vuex: design de API, suporte TypeScript, Composition API, desempenho, estratégias de migração e perguntas frequentes em entrevistas Vue para 2026.

Nuxt 4 em 2026: nova estrutura de diretórios e migração do Nuxt 3
Guia completo de migração para o Nuxt 4: estrutura de diretórios app/, camada singleton de busca de dados, melhorias no TypeScript e instruções passo a passo com exemplos de código.