React Testing Library mudou a forma como testamos interfaces: o foco sai da implementação e vai para o comportamento que o usuário vê e usa. Este guia cobre os princípios, queries, interações reais, assincronicidade, mocking e a armadilha da cobertura.
O princípio: testa o que o usuário vê
A ideia central é simples: testes devem validar o comportamento visível, não a estrutura interna. Não importa se o componente usa useState, useReducer ou classes. O que importa: o usuário consegue ver o elemento? Consegue interagir? Recebe feedback claro?
Em vez de afirmar "o botão tem o atributo aria-label='Salvar'", o teste verifica "o botão com o texto 'Salvar' existe e responde a cliques". Isso garante que refatorações internas não quebrem os testes — apenas mudanças no comportamento percebido pelo usuário.
Queries: encontrando elementos como um usuário faria
As queries são categorizadas por prioridade, da mais para a menos recomendada:
getBy: para elementos que sempre devem existir. Falha se não encontrar.queryBy: para elementos opcionais (ex: mensagens de erro). Não dispara exceção.findBy: para elementos assíncronos. Retorna promise.
No dia a dia, use getBy e findBy. queryBy serve para validar ausência inicial.
Exemplo prático:
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import TaskForm from '../TaskForm'
test('exibe erro quando título está vazio', async () => {
render(<TaskForm onSubmit={vi.fn()} />)
const createButton = screen.getByRole('button', { name: /criar task/i })
await userEvent.click(createButton)
const errorMessage = screen.getByText(/título é obrigatório/i)
expect(errorMessage).toBeInTheDocument()
})
Procura-se pelo texto visível e acessível, não por id ou seletor CSS. Isso torna testes resilientes a mudanças de estrutura HTML ou classes.
Cuidado com getByLabelText em campos sem label visível. Se só há placeholder (<input placeholder="Título" />), use getByPlaceholderText. Placeholder é guia, não rótulo.
User events: simule ação real, não evento DOM
fireEvent.click ignora comportamentos do navegador: focus, eventos de teclado, acessibilidade. Use @testing-library/user-event que simula interações reais — um clique dispara mouseenter, focus, mousedown, mouseup, click.
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import ModuleNavigation from '../ModuleNavigation'
test('navega para o próximo módulo e mantém foco', async () => {
const user = userEvent.setup()
render(<ModuleNavigation currentModule={1} totalModules={5} />)
const nextButton = screen.getByRole('button', { name: /próximo módulo/i })
await user.click(nextButton)
const activeModuleButton = screen.getByRole('button', {
name: /módulo 2/i,
selected: true
})
expect(activeModuleButton).toHaveFocus()
})
Isso é essencial para acessibilidade: se o foco não se move corretamente, usuários de leitor de tela encontram barreiras.
Async testing: quando o mundo não para
Interfaces modernas são assíncronas: carregamento de API, animações, eventos. Testing Library oferece ferramentas sem truques:
await screen.findByText('Sucesso'): espera até aparecer.waitFor(() => expect(...)): repete asserção até passar ou timeout.
test('mostra loading durante sincronização e atualiza após sucesso', async () => {
const user = userEvent.setup()
render(<SyncButton />)
const syncButton = screen.getByRole('button', { name: /sincronizar/i })
await user.click(syncButton)
const loadingButton = await screen.findByRole('button', { name: /sincronizando\.\.\./i })
expect(loadingButton).toBeDisabled()
await waitFor(() => {
expect(screen.getByRole('button', { name: /sincronizar/i })).toBeInTheDocument()
})
})
findBy e waitFor reagem ao DOM real, não a tempo fixo — testes rápidos e confiáveis.
Mocking API: controle sem dependência real
Testar componentes que consomem APIs exige isolamento. msw (Mock Service Worker) intercepta requisições na camada de rede, simulando respostas sem servidor.
// setupTests.js
import { setupServer } from 'msw/node'
import { handlers } from './mocks/handlers'
export const server = setupServer(...handlers)
beforeAll(() => server.listen())
afterEach(() => server.resetHandlers())
afterAll(() => server.close())
// mocks/handlers.js
import { rest } from 'msw'
export const handlers = [
rest.get('/api/models', (req, res, ctx) => {
return res(ctx.status(200), ctx.json([
{ id: 'llama-2', name: 'Llama 2' },
{ id: 'mistral-7b', name: 'Mistral 7B' }
]))
})
]
Vantagem do msw: intercepta tanto fetch quanto axios sem mudar lógica do componente. Prefira a vi.mock que testa implementação, não comportamento visível.
Cobertura: métrica útil, mas com cuidado
Cobertura mede linhas executadas, não se o comportamento certo foi testado. Fácil chegar a 100% testando só render. Não garante experiência do usuário.
Uso prático: filtre componentes críticos (checkout, pagamento, formulários) e verifique se têm testes neles. O número exato é secundário — o que importa é validar fluxos reais com mensagens acessíveis.
Conclusão: escreva testes que duram
Testar com React Testing Library documenta comportamento de forma que outros (ou você no futuro) saibam como o código deve se comportar.
Diferença entre teste fraco e forte:
- Fraco: "O botão deve disparar
handleClick" - Forte: "Ao clicar em 'Salvar', o usuário vê uma mensagem de confirmação"
O segundo não quebra se refatorar para useReducer. O comportamento percebido é o que importa.
Resumo prático:
- Comece com queries baseadas em texto acessível ou roles (
getByRole,getByText) - Use
userEventpara cliques, digitação, navegação - Prefira
findByewaitForpara assíncrono - Mocke APIs com
mswpara fidelidade total - Esqueça cobertura percentual. Foque em fluxos reais de usuário
Se um teste falha só por renomear botão ou mudar id, testa implementação — não comportamento. Refatore até falhar apenas quando o usuário vê algo quebrado.
Glossário do post
- react
- Biblioteca JavaScript para construir interfaces de usuário a partir de componentes reutilizáveis, mantida pela Meta.
- css
- Cascading Style Sheets: a linguagem usada para estilizar páginas web, controlando cores, fontes, layout e responsividade.
- html
- HyperText Markup Language: a linguagem de marcação que define a estrutura e o conteúdo de uma página web.
- dom
- Document Object Model: representação em árvore da página que o navegador expõe, permitindo que JavaScript leia e modifique o conteúdo.
- event
- Ocorrência relevante no sistema (clique, pedido, mensagem) que dispara ações assíncronas, base de arquiteturas event-driven.
- api
- Application Programming Interface: conjunto de regras que permite a um programa conversar com outro, expondo dados e funcionalidades de forma padronizada.
- service
- Unidade de software com responsabilidade bem definida, exposta por uma interface e reutilizável por outras partes do sistema.
