A certa altura, todo o programador React acaba com um componente que "funciona no browser" mas não tem testes. E fica assim durante meses, porque escrever testes para componentes React costumava parecer mais trabalhoso do que valia a pena.
Já estive nessa situação. Tive projetos cheios de componentes testados manualmente, refrescados no browser centenas de vezes, e colocados em produção com os dedos cruzados. Correu bem até deixar de correr.
Este artigo é sobre a abordagem de testes que finalmente fez sentido para mim: React Testing Library com Jest, escrevendo testes focados em comportamento e não em detalhes de implementação.
Porquê o React Testing Library?
Antes do RTL, o Enzyme era o padrão. O Enzyme permite inspecionar o estado interno, chamar métodos de ciclo de vida diretamente e fazer asserções sobre os internos do componente. Parece poderoso, e é. Mas também significa que os testes quebram sempre que se refatora, mesmo que o componente continue a fazer o mesmo para o utilizador.
O React Testing Library inverte o modelo. Consulta os elementos da mesma forma que um utilizador faria: por texto de label, por role, por texto visível. Dispara eventos como um utilizador. Faz asserções sobre o que o utilizador vê. Refatora os internos e os testes continuam a passar. Esse é o objetivo.
A Configuração
Se estás a usar Create React App ou Vite com o plugin oficial do React, o RTL e o Jest já estão incluídos ou são triviais de adicionar.
npm install --save-dev @testing-library/react @testing-library/jest-dom @testing-library/user-eventAdiciona isto ao teu ficheiro de configuração do Jest (ou cria um):
import '@testing-library/jest-dom';Isto importa os matchers personalizados como toBeInTheDocument(), toHaveValue() e toBeDisabled(), que tornam as asserções legíveis.
Escrever o Primeiro Teste
Começa com um exemplo simples. Um formulário de login com dois campos e um botão de submissão.
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import LoginForm from './LoginForm';
test('mostra erro quando submetido com campos vazios', async () => {
render(<LoginForm />);
await userEvent.click(screen.getByRole('button', { name: /submeter/i }));
expect(screen.getByText(/email é obrigatório/i)).toBeInTheDocument();
});Repara no que este teste não faz: não verifica o estado do componente, não chama nenhuma função interna e não se importa com a estrutura do componente. Renderiza o componente, age como um utilizador e verifica o que aparece no ecrã.
Consultar Elementos da Forma Correta
O RTL oferece vários métodos de consulta, e a ordem de preferência é importante.
- getByRole é a primeira escolha. Botões, links, inputs e cabeçalhos têm roles. Usa este sempre que possível.
- getByLabelText é ideal para inputs de formulário associados a um elemento label.
- getByText é útil para parágrafos, cabeçalhos ou qualquer conteúdo de texto visível.
- getByTestId é o último recurso. Se nada mais funcionar, adiciona um atributo
data-testid. Mas trata-o como um sinal de que o componente pode não ser suficientemente acessível.
Usar roles e labels tem um efeito secundário: empurra os componentes para uma melhor acessibilidade. Se não consegues consultar um botão pelo seu nome, talvez esse botão precise de um aria-label.
Interações Assíncronas
Os componentes reais obtêm dados, atrasam a renderização e atualizam de forma assíncrona. O RTL trata disto com as consultas findBy, que devolvem uma Promise e repetem até o elemento aparecer ou um timeout ser atingido.
test('carrega e exibe dados do utilizador', async () => {
render(<UserProfile userId="42" />);
expect(screen.getByText(/a carregar/i)).toBeInTheDocument();
const name = await screen.findByText(/paulo silva/i);
expect(name).toBeInTheDocument();
});Usa userEvent de @testing-library/user-event em vez de fireEvent. A API userEvent simula o comportamento real do browser de forma mais fiel: escrever dispara keydown, keypress e keyup. Clicar verifica que o elemento é realmente clicável. É mais lento mas mais preciso.
Simular Dependências Externas
A maioria dos componentes depende de algo externo: uma chamada a uma API, um hook personalizado, um contexto. A abordagem mais simples é simular o módulo que faz a chamada.
jest.mock('../api/users', () => ({
fetchUser: jest.fn().mockResolvedValue({ name: 'Paulo Silva', role: 'engineer' }),
}));Para o contexto, envolve o componente no provider dentro do teste. Podes criar um helper de renderização personalizado que envolve com providers comuns como auth, theme e router, para que cada ficheiro de teste não repita o mesmo boilerplate.
function renderWithProviders(ui) {
return render(
<AuthProvider>
<ThemeProvider>
{ui}
</ThemeProvider>
</AuthProvider>
);
}O Que Testar e O Que Ignorar
Nem todos os componentes precisam de testes. Componentes apresentacionais sem lógica têm pouco valor. Um componente que apenas renderiza um cabeçalho com uma prop é coberto pelos tipos TypeScript e uma verificação visual rápida.
Foca os testes em componentes que têm renderização condicional baseada em estado ou props, lidam com interações do utilizador como formulários, botões e modais, obtêm ou alteram dados, ou são usados em muitos lugares da codebase.
Um teste de integração cobrindo um fluxo completo do utilizador vale mais do que dez testes unitários superficiais a verificar que uma prop é passada corretamente.
Snapshots: Com Cuidado
O RTL funciona com os testes de snapshot do Jest, mas uso snapshots com moderação. São fáceis de escrever e fáceis de quebrar sem motivo válido. Um snapshot de um componente complexo torna-se rapidamente um fardo de manutenção.
Se os usares, mantém-nos pequenos. Faz snapshot de um único elemento renderizado, não de uma página inteira. E revê os diffs de snapshot com cuidado antes de os atualizar cegamente.
Uma Mudança de Mentalidade
O que fez os testes fazerem sentido para mim foi parar de pensar na implementação e começar a pensar no comportamento. O que é que este componente faz? O que é que o utilizador vê e com o que interage? Escreve o teste a partir dessa perspetiva.
Quando um teste quebra porque mudaste o nome de uma variável de estado interna, esse é um mau teste. Quando um teste quebra porque o formulário já não valida corretamente, esse é um bom teste a fazer o seu trabalho.
Começa com os teus componentes mais críticos. Escreve um teste. Depois outro. O hábito forma-se mais depressa do que pensas, e a confiança que dá ao refatorar é difícil de sobrevalorizar.



