Testes em React 2026: Vitest, React Testing Library e Melhores Práticas
Dominar os testes em React com Vitest e React Testing Library. Aprender padrões de teste de componentes, tratamento assíncrono, estratégias de mocking e melhores práticas para entrevistas técnicas em 2026.

Os testes em React durante 2026 se concentram no Vitest como o executor de testes dominante e no React Testing Library (RTL) como o padrão para testes de componentes. Essa combinação oferece ciclos de feedback rápidos, suporte nativo a módulos ES e testes que verificam comportamento ao invés de detalhes de implementação.
Escrever testes que se assemelhem a como os usuários interagem com os componentes. Consultar por roles acessíveis, labels e texto—não por classes CSS ou atributos data-testid. Essa abordagem detecta bugs reais e sobrevive ao refactoring.
Configuração do Vitest para Projetos React
O Vitest substituiu o Jest como o executor de testes preferido para aplicações React modernas. O suporte nativo a módulos ES elimina a sobrecarga de transformação, e a integração próxima com o Vite significa que a configuração espelha o ambiente de desenvolvimento.
Uma configuração do Vitest pronta para produção lida com a transformação JSX, aliases de caminhos e configuração do ambiente de testes sem boilerplate:
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
import tsconfigPaths from 'vite-tsconfig-paths'
export default defineConfig({
plugins: [react(), tsconfigPaths()],
test: {
// Use jsdom for DOM APIs and React rendering
environment: 'jsdom',
// Run setup file before each test file
setupFiles: ['./src/test/setup.ts'],
// Include only test files, exclude e2e
include: ['src/**/*.{test,spec}.{ts,tsx}'],
// Enable global test APIs (describe, it, expect)
globals: true,
// Generate coverage reports
coverage: {
provider: 'v8',
reporter: ['text', 'html', 'lcov'],
exclude: ['node_modules', 'src/test/**'],
},
},
})O arquivo de setup estende os matchers e configura a limpeza entre testes:
import '@testing-library/jest-dom/vitest'
import { cleanup } from '@testing-library/react'
import { afterEach, vi } from 'vitest'
// Clean up rendered components after each test
afterEach(() => {
cleanup()
})
// Mock window.matchMedia for responsive components
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: vi.fn().mockImplementation((query: string) => ({
matches: false,
media: query,
onchange: null,
addListener: vi.fn(),
removeListener: vi.fn(),
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
dispatchEvent: vi.fn(),
})),
})Essa configuração fornece uma base limpa para cada teste e lida com os mocks comuns de APIs do navegador que o jsdom não possui.
Testes de Componentes com React Testing Library
O React Testing Library impõe testar componentes da perspectiva do usuário. Ao invés de inspecionar estado interno ou valores de props, os testes interagem com a saída renderizada através de consultas de acessibilidade.
Considere um componente de busca que filtra resultados e exibe estados de carregamento:
import { useState } from 'react'
interface SearchResultsProps {
onSearch: (query: string) => Promise<string[]>
}
export function SearchResults({ onSearch }: SearchResultsProps) {
const [query, setQuery] = useState('')
const [results, setResults] = useState<string[]>([])
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
async function handleSearch() {
if (!query.trim()) return
setIsLoading(true)
setError(null)
try {
const data = await onSearch(query)
setResults(data)
} catch (e) {
setError('Search failed. Please try again.')
} finally {
setIsLoading(false)
}
}
return (
<div>
<label htmlFor="search-input">Search</label>
<input
id="search-input"
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
<button onClick={handleSearch} disabled={isLoading}>
{isLoading ? 'Searching...' : 'Search'}
</button>
{error && <p role="alert">{error}</p>}
<ul aria-label="Search results">
{results.map((result, i) => (
<li key={i}>{result}</li>
))}
</ul>
</div>
)
}Os testes verificam o comportamento do componente através das interações do usuário e estados assíncronos:
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { describe, it, expect, vi } from 'vitest'
import { SearchResults } from './SearchResults'
describe('SearchResults', () => {
it('displays results after successful search', async () => {
// Arrange: mock the search function
const mockSearch = vi.fn().mockResolvedValue(['React', 'Vue', 'Angular'])
const user = userEvent.setup()
render(<SearchResults onSearch={mockSearch} />)
// Act: type a query and click search
await user.type(screen.getByLabelText('Search'), 'framework')
await user.click(screen.getByRole('button', { name: 'Search' }))
// Assert: results appear in the list
expect(await screen.findByText('React')).toBeInTheDocument()
expect(screen.getByText('Vue')).toBeInTheDocument()
expect(mockSearch).toHaveBeenCalledWith('framework')
})
it('shows loading state during search', async () => {
// Create a promise that doesn't resolve immediately
let resolveSearch: (value: string[]) => void
const searchPromise = new Promise<string[]>((resolve) => {
resolveSearch = resolve
})
const mockSearch = vi.fn().mockReturnValue(searchPromise)
const user = userEvent.setup()
render(<SearchResults onSearch={mockSearch} />)
await user.type(screen.getByLabelText('Search'), 'test')
await user.click(screen.getByRole('button', { name: 'Search' }))
// Button shows loading state
expect(screen.getByRole('button', { name: 'Searching...' })).toBeDisabled()
// Resolve the search to clean up
resolveSearch!([])
})
it('displays error message on search failure', async () => {
const mockSearch = vi.fn().mockRejectedValue(new Error('Network error'))
const user = userEvent.setup()
render(<SearchResults onSearch={mockSearch} />)
await user.type(screen.getByLabelText('Search'), 'query')
await user.click(screen.getByRole('button', { name: 'Search' }))
// Error message appears with alert role for screen readers
expect(await screen.findByRole('alert')).toHaveTextContent(
'Search failed. Please try again.'
)
})
})A biblioteca userEvent simula interações de usuário realistas incluindo eventos de teclado e gerenciamento de foco, detectando bugs que eventos sintéticos não capturam.
Padrões de Testes Assíncronos e waitFor
Operações assíncronas requerem estratégias de espera explícitas. O React Testing Library fornece consultas findBy que combinam getBy com espera automática, além de waitFor para asserções complexas.
Preferir findBy sobre waitFor + getBy ao esperar elementos aparecerem. Reservar waitFor para asserções sobre elementos existentes que mudam de estado.
Padrões assíncronos comuns aparecem ao testar hooks e componentes de busca de dados:
import { render, screen, waitFor, waitForElementToBeRemoved } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { describe, it, expect, vi } from 'vitest'
// Wait for element to appear (preferred for new elements)
it('loads user profile on mount', async () => {
render(<UserProfile userId="123" />)
// findBy returns a promise, automatically waits up to 1000ms
const heading = await screen.findByRole('heading', { name: /john doe/i })
expect(heading).toBeInTheDocument()
})
// Wait for element to disappear
it('removes loading spinner after data loads', async () => {
render(<DataTable />)
// Wait for spinner to be removed from DOM
await waitForElementToBeRemoved(() => screen.queryByRole('progressbar'))
expect(screen.getByRole('table')).toBeInTheDocument()
})
// Wait for multiple conditions
it('enables submit button when form is valid', async () => {
const user = userEvent.setup()
render(<RegistrationForm />)
const submitButton = screen.getByRole('button', { name: 'Submit' })
expect(submitButton).toBeDisabled()
await user.type(screen.getByLabelText('Email'), 'user@example.com')
await user.type(screen.getByLabelText('Password'), 'SecurePass123!')
// waitFor retries the assertion until it passes or times out
await waitFor(() => {
expect(submitButton).toBeEnabled()
})
})
// Avoid false positives with queryBy for absence checks
it('does not show premium badge for free users', async () => {
render(<UserCard subscription="free" />)
// queryBy returns null instead of throwing, use for absence checks
expect(screen.queryByText('Premium')).not.toBeInTheDocument()
})A personalização de timeouts ajuda com operações lentas sem desacelerar toda a suite:
// Increase timeout for slow operations
await screen.findByText('Upload complete', {}, { timeout: 5000 })
await waitFor(
() => expect(mockSubmit).toHaveBeenCalled(),
{ timeout: 3000, interval: 100 }
)Pronto para mandar bem nas entrevistas de React / Next.js?
Pratique com nossos simuladores interativos, flashcards e testes tecnicos.
Estratégias de Mocking: Módulos, APIs e Hooks
As capacidades de mocking do Vitest lidam com dependências externas sem poluir o grafo de módulos. O mocking estratégico isola componentes de chamadas de rede, bibliotecas de terceiros e dependências complexas.
O mocking de módulos substitui imports no nível do bundler:
import { vi, describe, it, expect, beforeEach } from 'vitest'
import { fetchUserData } from './api'
// Mock the entire fetch module
vi.mock('./http-client', () => ({
httpClient: {
get: vi.fn(),
},
}))
import { httpClient } from './http-client'
describe('fetchUserData', () => {
beforeEach(() => {
vi.clearAllMocks()
})
it('transforms API response into user model', async () => {
// Type-safe mock implementation
vi.mocked(httpClient.get).mockResolvedValue({
data: { id: 1, first_name: 'John', last_name: 'Doe' },
})
const user = await fetchUserData(1)
expect(user).toEqual({
id: 1,
fullName: 'John Doe',
})
})
})Para hooks React que dependem de contexto ou estado externo, o utilitário renderHook permite testes isolados:
import { renderHook, waitFor } from '@testing-library/react'
import { describe, it, expect, vi } from 'vitest'
import { useAuth } from './useAuth'
import { AuthProvider } from './AuthContext'
describe('useAuth', () => {
it('returns authenticated user after login', async () => {
const mockLogin = vi.fn().mockResolvedValue({ id: 1, name: 'Alice' })
const { result } = renderHook(() => useAuth(), {
wrapper: ({ children }) => (
<AuthProvider loginFn={mockLogin}>{children}</AuthProvider>
),
})
expect(result.current.user).toBeNull()
expect(result.current.isAuthenticated).toBe(false)
await result.current.login('alice@example.com', 'password')
await waitFor(() => {
expect(result.current.user).toEqual({ id: 1, name: 'Alice' })
expect(result.current.isAuthenticated).toBe(true)
})
})
})MSW (Mock Service Worker) fornece mocking de API no nível da rede, permitindo testes que exercitam chamadas fetch reais:
import { http, HttpResponse } from 'msw'
export const handlers = [
http.get('/api/users/:id', ({ params }) => {
return HttpResponse.json({
id: params.id,
name: 'Test User',
})
}),
http.post('/api/login', async ({ request }) => {
const body = await request.json()
if (body.email === 'valid@example.com') {
return HttpResponse.json({ token: 'abc123' })
}
return HttpResponse.json(
{ error: 'Invalid credentials' },
{ status: 401 }
)
}),
]Testando Server Components do React 19
Server Components apresentam desafios únicos de teste, já que são executados no servidor e transmitem HTML para o cliente. Testes unitários diretos requerem uma abordagem diferente dos testes de componentes cliente.
Para Server Components que realizam busca de dados, testar a saída de renderização com fontes de dados mockadas:
import { describe, it, expect, vi } from 'vitest'
// Mock the data fetching function
vi.mock('./db', () => ({
getUser: vi.fn().mockResolvedValue({ id: 1, name: 'Server User' }),
}))
import { render, screen } from '@testing-library/react'
import { UserProfile } from './UserProfile.server'
describe('UserProfile Server Component', () => {
it('renders user data from database', async () => {
// Server components are async, await the component
const Component = await UserProfile({ userId: '1' })
render(Component)
expect(screen.getByRole('heading')).toHaveTextContent('Server User')
})
})Testes de integração com Playwright fornecem a cobertura mais confiável para Server Components ao testar o pipeline de renderização completo:
import { test, expect } from '@playwright/test'
test('server component renders with streamed data', async ({ page }) => {
await page.goto('/users/1')
// Wait for streaming to complete
await expect(page.getByRole('heading', { name: /profile/i })).toBeVisible()
await expect(page.getByText('Server User')).toBeVisible()
})Testando Custom Hooks com Estado Complexo
Hooks personalizados que gerenciam estado complexo, efeitos colaterais ou assinaturas externas se beneficiam de testes dedicados. O utilitário renderHook do React Testing Library lida com o ciclo de vida e atualizações dos hooks.
Considere um hook de busca com debounce que se coordena com uma API:
import { useState, useEffect, useCallback } from 'react'
export function useDebounceSearch<T>(
searchFn: (query: string) => Promise<T[]>,
delay = 300
) {
const [query, setQuery] = useState('')
const [results, setResults] = useState<T[]>([])
const [isLoading, setIsLoading] = useState(false)
useEffect(() => {
if (!query) {
setResults([])
return
}
setIsLoading(true)
const timeoutId = setTimeout(async () => {
try {
const data = await searchFn(query)
setResults(data)
} finally {
setIsLoading(false)
}
}, delay)
return () => clearTimeout(timeoutId)
}, [query, searchFn, delay])
return { query, setQuery, results, isLoading }
}Os testes verificam o comportamento do debounce e as transições de estado:
import { renderHook, act, waitFor } from '@testing-library/react'
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { useDebounceSearch } from './useDebounceSearch'
describe('useDebounceSearch', () => {
beforeEach(() => {
vi.useFakeTimers()
})
it('debounces search calls', async () => {
const mockSearch = vi.fn().mockResolvedValue(['result1', 'result2'])
const { result } = renderHook(() => useDebounceSearch(mockSearch, 300))
// Type multiple characters quickly
act(() => result.current.setQuery('r'))
act(() => result.current.setQuery('re'))
act(() => result.current.setQuery('rea'))
act(() => result.current.setQuery('reac'))
act(() => result.current.setQuery('react'))
// Search not called yet (debouncing)
expect(mockSearch).not.toHaveBeenCalled()
// Advance timers past debounce delay
await act(async () => {
vi.advanceTimersByTime(300)
})
// Only one search call with final query
expect(mockSearch).toHaveBeenCalledTimes(1)
expect(mockSearch).toHaveBeenCalledWith('react')
})
it('shows loading state during search', async () => {
let resolveSearch: (value: string[]) => void
const mockSearch = vi.fn().mockImplementation(
() => new Promise((resolve) => { resolveSearch = resolve })
)
const { result } = renderHook(() => useDebounceSearch(mockSearch, 100))
act(() => result.current.setQuery('test'))
await act(async () => {
vi.advanceTimersByTime(100)
})
expect(result.current.isLoading).toBe(true)
await act(async () => {
resolveSearch(['result'])
})
expect(result.current.isLoading).toBe(false)
expect(result.current.results).toEqual(['result'])
})
})Os fake timers do Vitest permitem testes determinísticos do comportamento dependente de tempo sem atrasos reais.
Perguntas de Entrevista sobre Testes em React
Entrevistas técnicas frequentemente investigam conhecimentos de testes. Essas perguntas avaliam a compreensão prática ao invés de conceitos teóricos.
Evitar testar detalhes de implementação como valores de estado ou métodos de componentes. Testes que quebram ao refatorar a estrutura interna fornecem falsa confiança e carga de manutenção.
Prioridade de consultas no React Testing Library
A prioridade de consulta recomendada segue acessibilidade: getByRole > getByLabelText > getByPlaceholderText > getByText > getByTestId. Consultas baseadas em roles garantem que os componentes permaneçam acessíveis e os testes sobrevivam a mudanças de markup.
Quando usar findBy vs waitFor
Consultas findBy esperam elementos aparecerem no DOM e retornam uma promise. Usá-las quando espera-se novos elementos após operações assíncronas. waitFor envolve asserções que podem não passar imediatamente, tentando novamente até sucesso ou timeout. Usar waitFor para asserções sobre mudanças de estado em elementos existentes.
Mocking de fetch vs MSW
O mocking direto de fetch (vi.spyOn(global, 'fetch')) funciona para casos simples, mas requer reimplementar objetos Response e tratamento de erros. MSW intercepta no nível da rede, permitindo que testes exercitem chamadas fetch reais, serialização de requisições e caminhos de código de tratamento de erros.
Para prática mais aprofundada desses conceitos, explorar as perguntas de entrevista de React Testing no SharpSkill.
Conclusão
- Configurar o Vitest com ambiente jsdom, arquivos de setup para extensões de matchers e relatórios de cobertura para visibilidade sobre lacunas de cobertura de testes
- Consultar componentes por roles e labels acessíveis para escrever testes que verificam comportamento voltado ao usuário e detectam bugs reais
- Usar consultas
findBypara elementos que aparecem após operações assíncronas,waitForpara asserções sobre estado em mudança - Fazer mock no nível apropriado: mocks de módulos para isolamento unitário, MSW para testes realistas da camada de rede
- Testar hooks personalizados com
renderHooke fake timers para verificar gerenciamento de estado complexo e efeitos colaterais - Server Components requerem renderização assíncrona em testes ou testes de integração completos com Playwright
- Leitura relacionada: Padrões avançados de React Hooks e TypeScript com React para padrões de teste type-safe
Comece a praticar!
Teste seus conhecimentos com nossos simuladores de entrevista e testes tecnicos.
Tags
Compartilhar
Artigos relacionados

React Compiler em 2026: memoização automática e perguntas de entrevista
Análise completa do React Compiler em 2026: pipeline de compilação, memoização automática, regras do React e perguntas frequentes em entrevistas técnicas.

Top 30 Perguntas de Entrevista React
Guia completo das 30 perguntas mais frequentes em entrevistas técnicas de React, com respostas detalhadas e exemplos de código.

Server Actions no Next.js 16 em 2026: Mutações, Revalidação e Perguntas de Entrevista
Como as Server Actions do Next.js 16 lidam com mutações, revalidação, estado de pendência, UI otimista e segurança, com as perguntas de entrevista que testam cada conceito.