Vue.js: Testes Unitários e E2E

Última atualização: 2026-08-26

Os testes unitários são a base para garantir a qualidade do código — eles permitem refatorar componentes sem medo de causar falhas, possibilitam que a integração contínua (CI) execute testes automaticamente e dão aos novos membros da equipe a confiança necessária para fazer alterações no código. O Vue 3 recomenda o Vitest (um framework de testes de última geração que é 10 vezes mais rápido que o Jest) + o Vue Test Utils (a ferramenta oficial para testes de componentes).

Os testes E2E simulam interações reais dos usuários — o Playwright é, atualmente, a ferramenta de testes E2E mais popular (mais rápida que o Cypress e compatível com vários navegadores). Este curso aborda uma estrutura abrangente de testes que inclui tanto testes unitários quanto testes E2E.

1. O que você vai aprender


2. Os pontos críticos de uma equipe que sofre de “fobia de refatoração”

(1) Problema: Se um componente for modificado incorretamente, todo o site trava

A equipe da Alice contava com um painel de administração de comércio eletrônico com mais de 100 componentes:

JS
// ❌ The "Broken" Version:Not tested,Alice I don't dare to refactor
// ProductCard.vue Changed 1 line, The entire home page is a blank screen
// No one noticed, Day 2 not until a user filed a complaint they discovered
// Team:"Don't move this component,We don't know what it will destroy."

O gerente de produto Charlie:

“Alice, precisamos de testes. Queremos poder refatorar sem medo. Precisamos de 1) testes de unidade para os componentes, 2) testes de ponta a ponta para os fluxos críticos.”

(2) Solução de utilitários de teste Vitest + Vue

JS
// tests/unit/ProductCard.test.js
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import ProductCard from '@/components/ProductCard.vue'

describe('ProductCard', () => {
  it('renders product name and price', () => {
    const product = { id: 1, name: 'iPhone', price: 999 }
    const wrapper = mount(ProductCard, { props: { product } })
    
    expect(wrapper.text()).toContain('iPhone')
    expect(wrapper.text()).toContain('$999')
  })
  
  it('emits add-to-cart event', async () => {
    const wrapper = mount(ProductCard, { 
      props: { product: { id: 1, name: 'iPhone', price: 999 } } 
    })
    
    await wrapper.find('button').trigger('click')
    
    expect(wrapper.emitted('add-to-cart')).toBeTruthy()
    expect(wrapper.emitted('add-to-cart')[0]).toEqual([1])
  })
})

Execute o teste:

BASH
$ npx vitest run
✓ ProductCard > renders product name and price
✓ ProductCard > emits add-to-cart event
2 passed

Executa em 5 segundos, sem alarmes falsos. Você saberá imediatamente após fazer uma alteração no código se ele está com defeito.

(3) Receita

Após adicionar os testes:


3. Configuração do Vitest

(1) Instalação

BASH
npm install -D vitest @vue/test-utils @vitest/coverage-v8 jsdom

(2) Configuração básica

TS
// vitest.config.ts
import { defineConfig } from 'vitest/config'
import vue from '@vitejs/plugin-vue'
import path from 'path'

export default defineConfig({
  plugins: [vue()],
  
  test: {
    // Test Environment
    environment: 'jsdom',  // or 'happy-dom' / 'node'
    
    // Matching Files
    include: ['tests/**/*.test.ts', 'src/**/*.test.ts'],
    
    // Coverage Rate
    coverage: {
      provider: 'v8',
      reporter: ['text', 'html', 'json'],
      exclude: ['node_modules/', 'tests/']
    },
    
    // Global setup
    setupFiles: ['./tests/setup.ts']
  },
  
  resolve: {
    alias: {
      '@': path.resolve(__dirname, 'src')
    }
  }
})

(3) 4 APIs principais

TS
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'

// 1. describe:Test Suite
describe('Calculator', () => { })

// 2. it:Single Test
it('adds 1 + 1 to equal 2', () => { })

// 3. expect:Assertion
expect(1 + 1).toBe(2)
expect(arr).toContain('apple')
expect(obj).toMatchObject({ name: 'Alice' })

// 4. vi:mock / spy
vi.fn()                  // mock Function
vi.mock('./api')         // mock Module
vi.spyOn(obj, 'method')  // spy Methods

(4) Vitest x Jest

Dimensão É Vitest
Tempo de inicialização 3–5 s < 1 s
Modo de escuta 2–3 s < 100 ms
Suporte ao ESM Requer configuração Nativo
TypeScript Requer o ts-jest Nativo
Compatibilidade Abrangente API compatível com o Jest
Avaliação Antigo ⭐⭐⭐⭐⭐

4. Testando os componentes do Vue Test Utils

(1) 5 APIs principais

JS
import { mount, shallowMount, RouterLinkStub } from '@vue/test-utils'

// 1. mount:Full Mount(Child components are also mounted)
const wrapper = mount(Component, { props: { ... } })

// 2. shallowMount: Shallow Mount (Child components are stubbed)
const wrapper = shallowMount(Component, { props: { ... } })

// 3. Find an Element
wrapper.find('button')              // First match
wrapper.findAll('li')              // All matches
wrapper.findComponent(MyComponent)  // Find Child Components
wrapper.get('#submit')             // Must be found(Error not found)

// 4. Trigger Event
await wrapper.find('button').trigger('click')
await wrapper.find('input').setValue('hello')

// 5. Assertion
expect(wrapper.text()).toContain('Hello')
expect(wrapper.find('h1').text()).toBe('Title')
expect(wrapper.emitted('add-to-cart')).toBeTruthy()

(2) 5 principais cenários de teste

JS
// 1. props Test
it('renders props correctly', () => {
  const wrapper = mount(MyComponent, { props: { title: 'Hello' } })
  expect(wrapper.text()).toContain('Hello')
})

// 2. emit Test
it('emits event on click', async () => {
  const wrapper = mount(MyComponent)
  await wrapper.find('button').trigger('click')
  expect(wrapper.emitted('submit')).toBeTruthy()
})

// 3. slot Test
it('renders default slot', () => {
  const wrapper = mount(MyComponent, {
    slots: { default: '<p>Slot content</p>' }
  })
  expect(wrapper.html()).toContain('Slot content')
})

// 4. Asynchronous Testing
it('loads data on mount', async () => {
  const wrapper = mount(MyComponent)
  await flushPromises()  // Wait for asynchronous completion
  expect(wrapper.text()).toContain('Loaded')
})

// 5. provide/inject Test
it('uses provided value', () => {
  const wrapper = mount(MyComponent, {
    provide: { theme: 'dark' }
  })
  expect(wrapper.find('.dark').exists()).toBe(true)
})

(3) 5 técnicas fundamentais de afirmação

JS
// 1. The text contains
expect(wrapper.text()).toContain('Hello')

// 2. class Existence
expect(wrapper.find('.active').exists()).toBe(true)

// 3. Elemental Presence
expect(wrapper.find('button').exists()).toBe(true)

// 4. Event Trigger
expect(wrapper.emitted('click')).toBeTruthy()
expect(wrapper.emitted('click')[0]).toEqual([arg1, arg2])

// 5. Asynchronous operation completed
await wrapper.vm.$nextTick()
await flushPromises()

5. Testes de ponta a ponta do Playwright

(1) Instalação

BASH
npm install -D @playwright/test
npx playwright install

(2) Configuração

TS
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test'

export default defineConfig({
  testDir: './tests/e2e',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: 'html',
  use: {
    baseURL: 'http://localhost:5173',
    trace: 'on-first-retry'
  },
  projects: [
    { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
    { name: 'firefox', use: { ...devices['Desktop Firefox'] } },
    { name: 'webkit', use: { ...devices['Desktop Safari'] } }
  ],
  webServer: {
    command: 'npm run dev',
    url: 'http://localhost:5173',
    reuseExistingServer: !process.env.CI
  }
})

(3) 5 principais cenários de testes de ponta a ponta

TS
// 1. Login Process
import { test, expect } from '@playwright/test'

test('user can login', async ({ page }) => {
  await page.goto('/login')
  await page.fill('input[name="username"]', 'alice@example.com')
  await page.fill('input[name="password"]', 'password123')
  await page.click('button[type="submit"]')
  
  await expect(page).toHaveURL('/dashboard')
  await expect(page.locator('h1')).toContainText('Welcome, Alice')
})

// 2. Add to Cart
test('add product to cart', async ({ page }) => {
  await page.goto('/products/1')
  await page.click('button:has-text("Add to Cart")')
  
  await expect(page.locator('.cart-count')).toHaveText('1')
})

// 3. Form Submission
test('submit form with validation', async ({ page }) => {
  await page.goto('/contact')
  await page.fill('input[name="email"]', 'invalid-email')
  await page.click('button[type="submit"]')
  
  await expect(page.locator('.error')).toBeVisible()
})

// 4. Route Redirection
test('navigate to products page', async ({ page }) => {
  await page.goto('/')
  await page.click('a:has-text("Products")')
  
  await expect(page).toHaveURL('/products')
})

// 5. Asynchronous Loading
test('loads data after navigation', async ({ page }) => {
  await page.goto('/dashboard')
  
  // Waiting for data to load
  await page.waitForSelector('.chart-loaded')
  await expect(page.locator('.chart-loaded')).toBeVisible()
})

(4) Executar testes de ponta a ponta

BASH
# Run all tests
npx playwright test

# Run a Specific File
npx playwright test tests/e2e/login.spec.ts

# Debug Mode(Open your browser)
npx playwright test --debug

# UI Pattern(Visualization)
npx playwright test --ui

6. Exemplo completo: 5 cenários principais de teste

▶ Exemplo: 1. Testando os 5 principais componentes

JS
// 1. props
it('renders props', () => { ... })

// 2. emit
it('emits event', async () => { ... })

// 3. slot
it('renders slot', () => { ... })

// 4. async
it('loads data', async () => { ... })

// 5. provide
it('uses provide', () => { ... })
▶ Experimente

▶ Exemplo: 2. 5 técnicas-chave de afirmação

JS
// 1. Text
expect(wrapper.text()).toContain('Hello')

// 2. class
expect(wrapper.find('.active').exists()).toBe(true)

// 3. Element
expect(wrapper.find('button').exists()).toBe(true)

// 4. Event
expect(wrapper.emitted('click')).toBeTruthy()

// 5. Asynchronous
await wrapper.vm.$nextTick()
▶ Experimente

▶ Exemplo: 3. 5 principais cenários de ponta a ponta (E2E)

TS
// 1. Log In
test('login', async ({ page }) => { ... })

// 2. Shopping Cart
test('add to cart', async ({ page }) => { ... })

// 3. Form
test('form submit', async ({ page }) => { ... })

// 4. Routing
test('navigate', async ({ page }) => { ... })

// 5. Asynchronous
test('async load', async ({ page }) => { ... })
▶ Experimente

▶ Exemplo: 4. Referência rápida para 5 erros comuns

Erro Sintoma Solução
O teste não consegue localizar o componente Erro de importação Verifique os aliases de caminho
Testes assíncronos sem espera Falha sempre Use await + flushPromises
Evento de acionamento não está funcionando O ouvinte não foi acionado "await" após "trigger"
Tempo limite de E2E Elemento não encontrado waitForSelector
Baixa cobertura Sem provas escritas Limite de cobertura obrigatório

▶ Exemplo: 5. Comparação das 5 principais ferramentas de teste

Ferramenta Velocidade Aplicação
Vitest ⭐⭐⭐⭐⭐ Testes unitários
Utilitários de teste para Vue ⭐⭐⭐⭐⭐ Testes de componentes
Dramaturgo ⭐⭐⭐⭐ Testes de ponta a ponta
Cypress ⭐⭐⭐ E2E (Lento)
Testing Library ⭐⭐⭐⭐ Testes de componentes

❓ Perguntas Frequentes

P: O que devo escolher, Vitest ou Jest? R: Vitest. Use o Vitest para todos os novos projetos (10 vezes mais rápido, suporte nativo a ESM/TS). Use o Jest para a manutenção de projetos legados.

P: O que devo escolher, mount ou shallowMount? R: Use shallowMount para testes unitários (mais rápidos e mais isolados). Use mount para testes de integração (mais realistas). Recomendação: use shallowMount para testes de componentes e mount para alguns componentes-chave.

P: Qual é a taxa de cobertura de teste adequada? R: 80% ou mais para componentes principais, 90% ou mais para utilitários e 60% ou mais no total. Não há necessidade de buscar 100% (isso é impraticável).

P: Playwright x Cypress — qual devo escolher? R: Playwright. É quase duas vezes mais rápido, é compatível com vários navegadores (Chromium, Firefox, WebKit) e possui uma API melhor. O Cypress é compatível apenas com navegadores baseados no Chromium.

P: Qual é a proporção entre testes unitários e testes E2E? R: 70% de testes unitários + 20% de testes de integração + 10% de testes E2E. Os testes E2E são lentos e frágeis, enquanto os testes unitários oferecem a melhor relação custo-benefício.

P: Todas as dependências devem ser simuladas durante os testes? R: Para testes de unidade, simule APIs externas (fetch, localStorage). Para testes de integração, use dependências reais. Para testes de ponta a ponta (E2E), use tudo como está.

P: Como faço para executar testes com CI? R: Adicione a etapa npm test no GitHub Actions ou no GitLab CI. Bloqueie a fusão de PRs caso os testes falhem.


📖 Resumo


📝 Exercícios

  1. Questões básicas (Dificuldade: ⭐)

    Escreva três testes para o ProductCard:

    • renderização de props
    • emitir um evento
    • Caso limite (o estoque é 0)
  2. Problemas avançados (Dificuldade: ⭐⭐)

    Escreva testes abrangentes para o carrinho de compras store:

    • 5 ações (addItem / removeItem / clear / load / save)
    • 3 métodos getter (itemCount / totalPrice / isEmpty)
    • Configuração do teste Pinia
  3. Problema de desafio (Dificuldade: ⭐⭐⭐)

    Implementar uma “estrutura de testes” abrangente:

    1. 10 testes unitários para componentes (3 a 5 testes por componente)
    2. 5 testes combináveis
    3. 5 testes de ponta a ponta (login / cadastro / pesquisa / carrinho de compras / finalização da compra)
    4. Limite de cobertura: 80%
    5. Integração de CI (GitHub Actions)
    6. Testes em vários navegadores com o Vitest e o Playwright
Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%