Next.js: Testes Unitários e de Integração

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

Testes não são opcionais — são o "airbag" das aplicações em produção: você não precisa deles no dia a dia, mas podem salvar sua vida quando realmente importa.

1. O Que Você Vai Aprender



2. A História Real de uma Engenheira Full-Stack

(1) Problema: Na véspera do lançamento, um único espaço fez a página de pagamento quebrar

Alice trabalha como engenheira full-stack em uma plataforma de e-commerce que atende o mercado do Oriente Médio. A plataforma processa mais de 50.000 pedidos por dia, e a equipe mantém um ciclo de lançamento de duas vezes por semana.

Durante o lançamento da última sexta-feira, uma alteração aparentemente inofensiva — adicionar um espaço extra na função de formatação de preço no componente OrderSummary — fez com que os valores de pagamento para usuários sauditas exibissem uma casa decimal a mais. Embora os dados em si não estivessem incorretos, o atendimento ao cliente recebeu mais de 200 reclamações, e três usuários cancelaram seus pedidos como resultado.

Para piorar a situação:

Alice estava determinada: ela precisava estabelecer um sistema de testes para evitar que esse tipo de problema acontecesse novamente.

(2) Solução com Vitest + Testing Library

Alice introduziu a stack de testes Vitest no projeto:

BASH
npm install -D vitest @testing-library/react @testing-library/jest-dom @vitejs/plugin-react msw

Então criou o primeiro teste:

TSX
import { render, screen } from '@testing-library/react'
import { OrderSummary } from './OrderSummary'

it('exibe o preço formatado corretamente', () => {
  render(<OrderSummary total={99.99} currency="SAR" />)
  expect(screen.getByText(/99\.99/)).toBeInTheDocument()
})

(3) Resultados

Dimensão Antes Depois
Cobertura de Código < 5% > 75%
Teste de regressão pré-lançamento Nenhum Executa automaticamente em 5 minutos
Taxa de Bugs em Produção 8–12 por mês 0–2 por mês
Confiança na Entrega de Novas Funcionalidades Baixa Alta


3. Configurando o Ambiente de Testes Vitest

Vitest é o framework de testes nativo do ecossistema Vite e é nativamente compatível com Next.js 16 (que usa Turbopack e Vite internamente).

100%
graph TB
    A[vitest.config.ts] --> B[Plugin React<br/>@vitejs/plugin-react]
    A --> C[Globais de Teste<br/>globals:true]
    A --> D[Ambiente<br/>jsdom]
    A --> E[Arquivos de Setup<br/>setup-test.ts]
    E --> F[Matcher jest-dom]
    E --> G[Inicialização MSW]
    
    style A fill:#cce5ff
    style F fill:#d4edda
    style G fill:#d4edda
Arquivo de Configuração Propósito Opções Principais
vitest.config.ts Configuração Principal de Teste environment: 'jsdom' Simula Navegador
setup-test.ts Inicialização Global Importa jest-dom, inicia o serviço MSW
tsconfig.json Suporte a Tipos types: ['vitest/globals']

(1) Configurar vitest.config.ts

TS
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
import path from 'path'

export default defineConfig({
  plugins: [react()],
  test: {
    environment: 'jsdom',
    globals: true,
    setupFiles: './src/__tests__/setup-test.ts',
    include: ['src/**/*.{test,spec}.{ts,tsx}'],
    coverage: {
      provider: 'v8',
      reporter: ['text', 'lcov'],
      thresholds: {
        statements: 70,
        branches: 60,
        functions: 70,
        lines: 70
      }
    }
  },
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src')
    }
  }
})

(2) Arquivo de Setup Global

TS
// src/__tests__/setup-test.ts
import '@testing-library/jest-dom/vitest'
import { cleanup } from '@testing-library/react'
import { afterEach, vi } from 'vitest'

afterEach(() => {
  cleanup()
})

vi.mock('next/navigation', () => ({
  useRouter: () => ({
    push: vi.fn(),
    replace: vi.fn(),
    refresh: vi.fn(),
    back: vi.fn(),
    forward: vi.fn()
  }),
  usePathname: () => '/',
  useSearchParams: () => new URLSearchParams()
}))

vi.mock('react-dom', async () => {
  const actual = await vi.importActual('react-dom')
  return { ...actual, useFormState: vi.fn() }
})

▶ Exemplo: Verificando que o Vitest está funcionando

TSX
// src/__tests__/basic.test.ts
import { render, screen } from '@testing-library/react'

function Hello({ name }: { name: string }) {
  return <h1>Olá, {name}!</h1>
}

it('renderiza mensagem de saudação', () => {
  render(<Hello name="Alice" />)
  expect(screen.getByText('Olá, Alice!')).toBeInTheDocument()
})
BASH
npx vitest run
💻 Saída:

TEXT 📖 Somente leitura
 ✓ src/__tests__/basic.test.ts (1 test) 12ms

 Test Files  1 passed (1)
      Tests  1 passed (1)

Saída:

TEXT 📖 Somente leitura
 ✓ src/__tests__/basic.test.ts (1 test) 12ms

 Test Files  1 passed (1)
       Tests  1 passed (1)

▶ Exemplo: Adicionando script de teste ao package.json

JSON
{
  "scripts": {
    "test": "vitest run",
    "test:watch": "vitest",
    "test:coverage": "vitest run --coverage"
  }
}

Saída:

TEXT 📖 Somente leitura
Estrutura JSON definindo três scripts npm: "test" (vitest run), "test:watch" (vitest) e "test:coverage" (vitest run --coverage).


4. Teste de Componentes com @testing-library/react

A filosofia central da Testing Library: Teste o que os usuários veem e com o que interagem, em vez de detalhes de implementação.

(1) Consultas com "render" e "screen"

100%
graph LR
    A[render Componentes] --> B[screen Busca]
    B --> C{Tipo de Consulta}
    C --> D[getByText Texto]
    C --> E[getByRole Semântica]
    C --> F[getByTestId ID de Teste]
    C --> G[getByPlaceholderText Placeholder]
    
    D --> H[Asserção expect]
    E --> H
    F --> H
    G --> H
Método de Consulta Cenários Aplicáveis Exemplo
getByText Conteúdo de texto getByText('Enviar Pedido')
getByRole Elementos Semânticos getByRole('button', { name: /Enviar/i })
getByPlaceholderText Dica de Campo de Entrada getByPlaceholderText('Digite o email')
getByTestId Elemento não semântico getByTestId('total-pedido')
queryByText Sem asserções expect(queryByText('Erro')).not.toBeInTheDocument()

(2) Testando Componentes Server (Renderização Síncrona)

Componentes Server não exigem JavaScript no lado do cliente; você pode fazer asserções diretamente na saída HTML durante o teste:

TSX
// src/app/products/page.tsx
async function ProductsPage() {
  const res = await fetch('https://api.example.com/products')
  const products = await res.json()
  
  return (
    <ul>
      {products.map((p: { id: number; name: string; price: number }) => (
        <li key={p.id} data-testid="product-item">
          {p.name} — R$ {p.price}
        </li>
      ))}
    </ul>
  )
}

export default ProductsPage
TSX
// src/__tests__/products-page.test.tsx
import { render, screen } from '@testing-library/react'
import ProductsPage from '@/app/products/page'

// Mock do fetch global
global.fetch = vi.fn().mockResolvedValue({
  json: () => Promise.resolve([
    { id: 1, name: 'iPhone 16', price: 999 },
    { id: 2, name: 'Samsung S26', price: 899 }
  ])
})

it('renderiza lista de produtos do servidor', async () => {
  const page = await ProductsPage()
  render(page)
  
  expect(screen.getByText('iPhone 16')).toBeInTheDocument()
  expect(screen.getByText('Samsung S26')).toBeInTheDocument()
  expect(screen.getAllByTestId('product-item')).toHaveLength(2)
})

▶ Exemplo: Testando Componentes Client (Incluindo Interações do Usuário)

Saída:

TEXT 📖 Somente leitura
Renderiza a UI do componente.
TSX
// src/components/Counter.tsx
'use client'

import { useState } from 'react'

export function Counter({ initial = 0 }) {
  const [count, setCount] = useState(initial)
  
  return (
    <div>
      <p data-testid="count">Contagem: {count}</p>
      <button onClick={() => setCount(c => c + 1)}>Incrementar</button>
      <button onClick={() => setCount(c => c - 1)}>Decrementar</button>
    </div>
  )
}

Saída:

TEXT 📖 Somente leitura
Um componente interativo com gerenciamento de estado.
Texto visível: Contagem: {count}
TSX
// src/__tests__/counter.test.tsx
import { render, screen, fireEvent } from '@testing-library/react'
import { Counter } from '@/components/Counter'

describe('Counter', () => {
  it('renderiza com valor inicial', () => {
    render(<Counter initial={5} />)
    expect(screen.getByTestId('count')).toHaveTextContent('Contagem: 5')
  })
  
  it('incrementa ao clicar no botão', () => {
    render(<Counter initial={0} />)
    fireEvent.click(screen.getByText('Incrementar'))
    expect(screen.getByTestId('count')).toHaveTextContent('Contagem: 1')
  })
  
  it('decrementa ao clicar no botão', () => {
    render(<Counter initial={10} />)
    fireEvent.click(screen.getByText('Decrementar'))
    expect(screen.getByTestId('count')).toHaveTextContent('Contagem: 9')
  })
})


5. Matchers Personalizados no jest-dom

O jest-dom fornece matchers de asserção DOM semânticos, tornando o código de teste mais próximo da linguagem natural.

Matcher Função Exemplo
toBeInTheDocument() Elementos no DOM expect(el).toBeInTheDocument()
toHaveTextContent(text) Correspondência de Conteúdo de Texto expect(el).toHaveTextContent('Olá')
toBeVisible() Elemento Visível expect(el).toBeVisible()
toBeDisabled() Botão desabilitado expect(btn).toBeDisabled()
toHaveClass(cls) Correspondência de classe CSS expect(el).toHaveClass('active')
toHaveAttribute(attr) Correspondência de Atributo expect(input).toHaveAttribute('type', 'email')
toHaveValue(val) Correspondência de Valor de Formulário expect(input).toHaveValue('teste@exemplo.com')

▶ Exemplo: Teste de Validação de Formulário

Saída:

TEXT 📖 Somente leitura
Renderiza a UI do componente.
TSX
import { render, screen, fireEvent } from '@testing-library/react'
import { LoginForm } from '@/components/LoginForm'

describe('Validação do LoginForm', () => {
  it('exibe erro para email vazio', () => {
    render(<LoginForm />)
    fireEvent.click(screen.getByRole('button', { name: /Entrar/i }))
    expect(screen.getByText(/Por favor, insira seu endereço de email/i)).toBeInTheDocument()
    expect(screen.getByText(/Por favor, insira seu endereço de email/i)).toBeVisible()
  })
  
  it('desabilita o botão enviar durante o carregamento', () => {
    render(<LoginForm />)
    fireEvent.change(screen.getByPlaceholderText('Digite seu endereço de email'), {
      target: { value: 'alice@exemplo.com' }
    })
    fireEvent.click(screen.getByRole('button', { name: /Entrar/i }))
    expect(screen.getByRole('button', { name: /Entrando.../i })).toBeDisabled()
  })
  
  it('limpa o erro após entrada válida', () => {
    render(<LoginForm />)
    fireEvent.click(screen.getByRole('button', { name: /Entrar/i }))
    expect(screen.getByText(/Por favor, insira seu endereço de email/i)).toBeInTheDocument()
    fireEvent.change(screen.getByPlaceholderText('Digite seu endereço de email'), {
      target: { value: 'alice@exemplo.com' }
    })
    expect(screen.queryByText(/Por favor, insira seu endereço de email/i)).not.toBeInTheDocument()
  })
})

Saída:

TEXT 📖 Somente leitura
Renderiza a UI do componente ▶ Exemplo: Teste de Validação de Formulário conforme descrito na seção.


6. Testando Server Actions (Mock do Prisma + revalidatePath)

Server Actions precisam simular operações de banco de dados e funções de invalidação de cache.

100%
graph TB
    A[Testar Server Action] --> B[Mock do Prisma Client]
    A --> C[Mock do revalidatePath]
    A --> D[Mock do redirect]
    B --> E[Retornar Dados Simulados]
    C --> F[Verificar número de chamadas/Parâmetros]
    D --> G[Verificar Caminho de Redirecionamento]
    
    style A fill:#cce5ff
    style B fill:#d4edda
    style C fill:#fff3cd

(1) Ferramenta de Mock do Prisma

TS
// src/__tests__/utils/mock-prisma.ts
import { vi } from 'vitest'

export function createMockPrisma() {
  return {
    task: {
      findMany: vi.fn().mockResolvedValue([
        { id: '1', title: 'Tarefa de Teste', status: 'TODO', projectId: 'p1' }
      ]),
      create: vi.fn().mockImplementation(({ data }) => Promise.resolve({
        id: 'novo-id',
        ...data,
        createdAt: new Date()
      })),
      update: vi.fn().mockImplementation(({ data }) => Promise.resolve(data)),
      delete: vi.fn().mockResolvedValue({ id: 'id-excluido' })
    },
    project: {
      findUnique: vi.fn().mockResolvedValue({
        id: 'p1',
        name: 'Projeto de Teste',
        tasks: []
      })
    },
    $transaction: vi.fn().mockImplementation((cb) => cb(createMockPrisma()))
  } as any
}

(2) Exemplo de Teste de uma Server Action

TS
// src/actions/task.ts
'use server'

import { prisma } from '@/lib/prisma'
import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'
import { z } from 'zod'

const taskSchema = z.object({
  title: z.string().min(1, 'O título não pode ficar em branco.'),
  projectId: z.string().min(1),
  status: z.enum(['TODO', 'IN_PROGRESS', 'DONE'])
})

export async function createTask(formData: FormData) {
  const data = Object.fromEntries(formData)
  const parsed = taskSchema.safeParse(data)
  
  if (!parsed.success) {
    return { error: parsed.error.flatten().fieldErrors }
  }
  
  await prisma.task.create({ data: parsed.data })
  revalidatePath(`/projects/${parsed.data.projectId}`)
  redirect(`/projects/${parsed.data.projectId}`)
}

▶ Exemplo: Teste Completo de Server Action

Saída:

TEXT 📖 Somente leitura
Valida a entrada com schema Zod antes de processar.
Grava no banco de dados e atualiza o cache da página afetada.
Retorna objeto de erro se a validação ou processamento falhar.
TS
// src/__tests__/actions/task.test.ts
import { createTask } from '@/actions/task'
import { createMockPrisma } from '../utils/mock-prisma'
import { vi, describe, it, expect, beforeEach } from 'vitest'

vi.mock('@/lib/prisma', () => ({ prisma: createMockPrisma() }))
vi.mock('next/cache', () => ({ revalidatePath: vi.fn() }))
vi.mock('next/navigation', () => ({ redirect: vi.fn() }))

describe('createTask', () => {
  beforeEach(() => {
    vi.clearAllMocks()
  })
  
  it('cria uma tarefa com sucesso', async () => {
    const formData = new FormData()
    formData.append('title', 'Escrever Documentação de Teste')
    formData.append('projectId', 'p1')
    formData.append('status', 'TODO')
    
    const result = await createTask(formData)
    expect(result).toBeUndefined()
  })
  
  it('retorna erro de validação para título vazio', async () => {
    const formData = new FormData()
    formData.append('title', '')
    formData.append('projectId', 'p1')
    formData.append('status', 'TODO')
    
    const result = await createTask(formData)
    expect(result).toHaveProperty('error')
    expect(result.error.title).toBeDefined()
  })
  
  it('chama revalidatePath após a criação', async () => {
    const formData = new FormData()
    formData.append('title', 'Nova Tarefa')
    formData.append('projectId', 'p1')
    formData.append('status', 'TODO')
    
    await createTask(formData)
    const { revalidatePath } = await import('next/cache')
    expect(revalidatePath).toHaveBeenCalledWith('/projects/p1')
  })
})

Saída:

TEXT 📖 Somente leitura
Cria um novo registro no banco de dados.


7. MSW Mock de API no Lado do Servidor

MSW (Mock Service Worker) intercepta requisições de rede, permitindo testar a lógica de recuperação de dados sem precisar iniciar o backend real.

100%
graph LR
    A[Componente Inicia fetch] --> B[MSW Service Worker]
    B --> C{Correspondência de Requisição}
    C -->|Handler Correspondente| D[Retorna Dados Mock]
    C -->|Sem Correspondência| E[Passa para a rede real]
    
    style B fill:#cce5ff
    style D fill:#d4edda
Conceito Descrição Exemplo
Handler Manipulador de requisição http.get('/api/products', resolver)
Resolver Retorna uma resposta mock return HttpResponse.json([...])
Server Servidor mock Node.js setupServer(...handlers)
Browser MSW no navegador setupWorker(...handlers)

(1) Definindo Handlers Mock

TS
// src/mocks/handlers.ts
import { http, HttpResponse } from 'msw'

const API_BASE = 'https://api.example.com'

export const handlers = [
  // GET /api/products
  http.get(`${API_BASE}/products`, () => {
    return HttpResponse.json([
      { id: 1, name: 'iPhone 16', price: 999, category: 'Eletrônicos' },
      { id: 2, name: 'Samsung S26', price: 899, category: 'Eletrônicos' }
    ])
  }),
  
  // POST /api/orders
  http.post(`${API_BASE}/orders`, async ({ request }) => {
    const body = await request.json()
    return HttpResponse.json({
      id: 'pedido-123',
      ...body as any,
      status: 'confirmado',
      createdAt: new Date().toISOString()
    })
  }),
  
  // GET /api/users/:id
  http.get(`${API_BASE}/users/:id`, ({ params }) => {
    return HttpResponse.json({
      id: params.id,
      name: 'Alice Wang',
      email: 'alice@exemplo.com',
      role: 'admin'
    })
  }),
  
  // Erro 500 para testar error boundaries
  http.get(`${API_BASE}/errors/internal`, () => {
    return HttpResponse.json(
      { error: 'Erro Interno do Servidor' },
      { status: 500 }
    )
  })
]

(2) Integração no Setup de Teste

TS
// src/mocks/server.ts
import { setupServer } from 'msw/node'
import { handlers } from './handlers'

export const server = setupServer(...handlers)
TS
// src/__tests__/setup-test.ts (Versão Completa)
import '@testing-library/jest-dom/vitest'
import { cleanup } from '@testing-library/react'
import { afterEach, afterAll, beforeAll, vi } from 'vitest'
import { server } from '@/mocks/server'

beforeAll(() => server.listen({ onUnhandledRequest: 'warn' }))
afterEach(() => { cleanup(); server.resetHandlers() })
afterAll(() => server.close())

// Mock next/navigation
vi.mock('next/navigation', () => ({
  useRouter: () => ({
    push: vi.fn(), replace: vi.fn(),
    refresh: vi.fn(), back: vi.fn(), forward: vi.fn()
  }),
  usePathname: () => '/',
  useSearchParams: () => new URLSearchParams()
}))

▶ Exemplo: Testando a Interceptação de API com MSW

Saída:

TEXT 📖 Somente leitura
Módulo TypeScript executado com sucesso.
TSX
// src/__tests__/api-integration.test.tsx
import { render, screen, waitFor } from '@testing-library/react'
import { http, HttpResponse } from 'msw'
import { server } from '@/mocks/server'
import ProductsPage from '@/app/products/page'

describe('ProductsPage com MSW', () => {
  it('renderiza produtos da API mockada', async () => {
    const page = await ProductsPage()
    render(page)
    
    expect(screen.getByText('iPhone 16')).toBeInTheDocument()
    expect(screen.getByText('Samsung S26')).toBeInTheDocument()
  })
  
  it('trata lista de produtos vazia', async () => {
    server.use(
      http.get('https://api.example.com/products', () => {
        return HttpResponse.json([])
      })
    )
    
    const page = await ProductsPage()
    render(page)
    await waitFor(() => {
      expect(screen.queryByTestId('product-item')).not.toBeInTheDocument()
    })
  })
  
  it('exibe erro em caso de falha da API', async () => {
    server.use(
      http.get('https://api.example.com/products', () => {
        return HttpResponse.json(
          { error: 'Serviço indisponível' },
          { status: 503 }
        )
      })
    )
    
    await expect(ProductsPage()).rejects.toThrow()
  })
})

Saída:

TEXT 📖 Somente leitura
Renderiza a UI do componente ▶ Exemplo: Testando a Interceptação de API com MSW conforme descrito na seção.


8. Exemplo Completo: Teste Full-Stack de um App de Tarefas

TSX
// src/__tests__/todo-comprehensive.test.ts
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import { http, HttpResponse } from 'msw'
import { server } from '@/mocks/server'
import { createMockPrisma } from './utils/mock-prisma'
import { describe, it, expect, vi, beforeEach } from 'vitest'

// ============================================
// Teste Abrangente: Componentes + Action + API do App de Tarefas
// ============================================

// --- 1. Mock de Dependências ---
vi.mock('@/lib/prisma', () => ({ prisma: createMockPrisma() }))
vi.mock('next/cache', () => ({ revalidatePath: vi.fn() }))
vi.mock('next/navigation', () => ({
  redirect: vi.fn(),
  useRouter: () => ({ push: vi.fn(), refresh: vi.fn() })
}))

// --- 2. Componentes de Tarefas ---
function TodoItem({ id, title, done, onToggle }: {
  id: string; title: string; done: boolean
  onToggle: (id: string) => void
}) {
  return (
    <div data-testid="todo-item">
      <span style={{ textDecoration: done ? 'line-through' : 'none' }}>
        {title}
      </span>
      <button onClick={() => onToggle(id)}>
        {done ? 'Desfazer' : 'Concluir'}
      </button>
    </div>
  )
}

function TodoList({ todos }: { todos: Array<{ id: string; title: string; done: boolean }> }) {
  return (
    <div>
      {todos.map(t => (
        <TodoItem key={t.id} {...t} onToggle={(id) => {
          const idx = todos.findIndex(t => t.id === id)
          todos[idx].done = !todos[idx].done
        }} />
      ))}
    </div>
  )
}

// --- 3. Casos de Teste ---
describe('App de Tarefas', () => {
  const mockTodos = [
    { id: '1', title: 'Aprender testes no Next.js', done: false },
    { id: '2', title: 'Escrever testes unitários', done: true },
    { id: '3', title: 'Configurar pipeline CI', done: false }
  ]
  
  it('renderiza todas as tarefas', () => {
    render(<TodoList todos={mockTodos} />)
    expect(screen.getAllByTestId('todo-item')).toHaveLength(3)
    expect(screen.getByText('Aprender testes no Next.js')).toBeInTheDocument()
  })
  
  it('exibe tachado para itens concluídos', () => {
    render(<TodoList todos={mockTodos} />)
    const doneItem = screen.getByText('Escrever testes unitários')
    expect(doneItem).toHaveStyle('text-decoration: line-through')
  })
  
  it('alterna tarefa ao clicar no botão', () => {
    render(<TodoList todos={mockTodos} />)
    const buttons = screen.getAllByRole('button')
    fireEvent.click(buttons[0])
    expect(buttons[0]).toHaveTextContent('Desfazer')
  })
  
  it('possui rótulos de botão corretos', () => {
    render(<TodoList todos={mockTodos} />)
    const buttons = screen.getAllByRole('button')
    expect(buttons[0]).toHaveTextContent('Concluir')
    expect(buttons[1]).toHaveTextContent('Desfazer')
  })
})

// --- 4. Teste de API com MSW ---
describe('Mock da API de Tarefas', () => {
  it('busca tarefas da API mockada', async () => {
    server.use(
      http.get('https://api.example.com/todos', () => {
        return HttpResponse.json(mockTodos)
      })
    )
    
    const res = await fetch('https://api.example.com/todos')
    const data = await res.json()
    expect(data).toHaveLength(3)
    expect(data[0].title).toBe('Aprender testes no Next.js')
  })
  
  it('trata erro da API com elegância', async () => {
    server.use(
      http.get('https://api.example.com/todos', () => {
        return HttpResponse.json(null, { status: 500 })
      })
    )
    
    const res = await fetch('https://api.example.com/todos')
    expect(res.status).toBe(500)
  })
})
💻 Saída:

TEXT 📖 Somente leitura
 ✓ src/__tests__/todo-comprehensive.test.ts (7 tests) 45ms

 Test Files  1 passed (1)
      Tests  7 passed (7)

❓ Perguntas Frequentes

P: Qual é a diferença entre Vitest e Jest? R: O Vitest compartilha sua configuração e ecossistema de plugins com o Vite, inicia de 10 a 20 vezes mais rápido que o Jest (com suporte nativo a ESM) e tem uma API de sintaxe compatível com o Jest (a API globals suporta expect/describe/it). O Next.js 16 usa Turbopack (parte do ecossistema Vite) internamente, então o Vitest é a escolha mais natural.

P: Como testo um Componente Server que usa cookies() ou headers()? R: Essas funções dependem do contexto de requisição do Next.js, que precisa ser mockado nos testes. Recomendamos extrair a lógica de recuperação de dados para uma Server Action ou camada de API separada e, em seguida, testar a renderização do componente e a lógica de negócio separadamente.

P: Qual é a diferença entre MSW e mock manual de fetch? R: O MSW intercepta requisições de rede no nível do Service Worker, permitindo testar cadeias de chamadas fetch reais sem fazer alterações no código do componente. Mockar global.fetch manualmente é simples, mas não consegue lidar com correspondência complexa de requisições, serialização de respostas ou cenários de erro.

P: Qual é um limite razoável para cobertura de testes? R: Recomendamos uma abordagem em fases: Defina a meta em 50% para a Fase 1 (componentes principais + server actions), aumente para 70% na Fase 2 (cobrindo branches e casos de borda) e busque 80%+ para projetos em produção. Lembre-se de que a cobertura não é o objetivo final; caminhos críticos (pagamento/login/gravação de dados) devem ter cobertura de 100%.

P: Como testo um formulário que usa useActionState? R: useActionState depende de useFormState no React 19, então você precisa mockar useFormState no react-dom durante os testes. Recomendamos usar fireEvent.submit para disparar o envio do formulário e, em seguida, fazer asserções sobre o estado da UI após o envio (mensagens de sucesso/erro).

P: Qual é a diferença entre fireEvent e userEvent na Testing Library? R: fireEvent dispara eventos DOM diretamente (como click), enquanto userEvent simula interações de usuário mais realistas (como entrada de teclado e gerenciamento de foco). Recomendamos usar userEvent para testes em produção (pois imita melhor a experiência do usuário) e fireEvent para testes unitários (pois é mais leve).


📖 Resumo


📝 Exercícios

  1. Exercício Básico (⭐): Crie um vitest.config.ts que inclua o plugin React, o ambiente jsdom e um alias de caminho para @/, depois escreva o teste mais simples render(<div>Olá</div>) para verificar se o matcher Jest-DOM está funcionando corretamente.

  2. Exercício Avançado (⭐⭐): Escreva testes abrangentes para as Server Actions do seu projeto (como createUser ou submitOrder): mocke o método create do Prisma, verifique se revalidatePath é chamado e teste a resposta de erro quando a validação do Zod falha.

  3. Desafio (⭐⭐⭐): Use o MSW para construir três handlers mock (GET /api/tasks, POST /api/tasks, DELETE /api/tasks/:id) e, em seguida, escreva pelo menos 8 casos de teste para o componente TaskList, que inclui funcionalidade de recuperação, criação e exclusão de dados (incluindo carregamento inicial, lista vazia e tratamento de erros).

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%