Testing Library: testes de integração no React
1. Introdução aos Testes de Integração com Testing Library
No ecossistema React, os testes podem ser classificados em três grandes categorias: testes unitários, que verificam funções ou componentes isolados; testes de integração, que validam a interação entre múltiplos componentes e serviços; e testes end-to-end (E2E), que simulam o fluxo completo do usuário no navegador. A Testing Library se destaca como a escolha padrão para testes de integração porque incentiva uma abordagem centrada no usuário: em vez de testar detalhes de implementação, você testa o comportamento visível e acessível da interface.
A filosofia da Testing Library é simples: quanto mais seus testes se parecem com a forma como o usuário interage com a aplicação, mais confiança eles podem lhe dar. Para começar, instale os pacotes necessários:
npm install --save-dev @testing-library/react @testing-library/jest-dom @testing-library/user-event
2. Configuração do Ambiente de Teste
Em projetos criados com Create React App (CRA), a configuração já vem pronta. Para Vite ou Next.js, você precisa configurar o Jest manualmente. Crie um arquivo jest.config.js:
module.exports = {
testEnvironment: 'jsdom',
setupFilesAfterSetup: ['<rootDir>/src/setupTests.js'],
moduleNameMapper: {
'\\.(css|less|scss)$': 'identity-obj-proxy',
},
};
No arquivo setupTests.js, importe as extensões do jest-dom:
import '@testing-library/jest-dom';
Para mockar módulos como React Router ou Redux, use jest.mock() no início do arquivo de teste ou crie providers customizados.
3. Renderização e Consultas (Queries)
A função render() do Testing Library renderiza um componente no DOM virtual. Use screen para acessar os elementos renderizados:
import { render, screen } from '@testing-library/react';
import MeuComponente from './MeuComponente';
test('renderiza o título', () => {
render(<MeuComponente />);
const titulo = screen.getByText('Bem-vindo');
expect(titulo).toBeInTheDocument();
});
As queries disponíveis são: getBy, findBy e queryBy. A principal diferença está no comportamento quando o elemento não é encontrado: getBy lança erro, queryBy retorna null e findBy retorna uma Promise (útil para elementos assíncronos). Sempre priorize getByRole e outras queries baseadas em acessibilidade:
const botao = screen.getByRole('button', { name: /enviar/i });
const campo = screen.getByLabelText('Nome');
Evite data-testid a menos que seja estritamente necessário, pois ele não representa como o usuário enxerga a interface.
4. Simulação de Interações do Usuário
Para simular eventos do usuário, use @testing-library/user-event em vez do fireEvent nativo. O user-event simula interações mais realistas, como digitação caractere por caractere e cliques completos:
import userEvent from '@testing-library/user-event';
test('preenche formulário e submete', async () => {
const user = userEvent.setup();
render(<Formulario />);
await user.type(screen.getByLabelText('Email'), 'teste@email.com');
await user.click(screen.getByRole('button', { name: /enviar/i }));
expect(screen.getByText('Enviado com sucesso')).toBeInTheDocument();
});
Para testar navegação entre rotas, envolva o componente com MemoryRouter:
import { MemoryRouter } from 'react-router-dom';
render(
<MemoryRouter initialEntries={['/']}>
<App />
</MemoryRouter>
);
5. Testando Estados Assíncronos e Chamadas de API
Para testar chamadas assíncronas, use waitFor ou findBy. O findBy é uma combinação de getBy com waitFor implícito:
test('carrega dados da API', async () => {
render(<ListaUsuarios />);
// Aguarda o elemento aparecer
const usuario = await screen.findByText('João');
expect(usuario).toBeInTheDocument();
});
Para mockar requisições HTTP, utilize o Mock Service Worker (MSW), que intercepta requisições no nível do service worker:
import { rest } from 'msw';
import { setupServer } from 'msw/node';
const server = setupServer(
rest.get('/api/usuarios', (req, res, ctx) => {
return res(ctx.json([{ id: 1, nome: 'João' }]));
})
);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
Teste os três estados: loading, sucesso e erro. Para simular erro, basta modificar o handler:
server.use(
rest.get('/api/usuarios', (req, res, ctx) => {
return res(ctx.status(500));
})
);
6. Testando Componentes com Contexto e Estado Global
Componentes que dependem de contexto (React Context, Redux, Zustand) precisam ser renderizados dentro de seus providers. Crie uma função render customizada para evitar repetição:
import { render } from '@testing-library/react';
import { ThemeProvider } from './contexts/ThemeContext';
import { Provider } from 'react-redux';
import { store } from './store';
const AllTheProviders = ({ children }) => {
return (
<Provider store={store}>
<ThemeProvider>{children}</ThemeProvider>
</Provider>
);
};
const customRender = (ui, options) =>
render(ui, { wrapper: AllTheProviders, ...options });
export { customRender as render };
Agora, nos testes, importe seu render customizado:
import { render, screen } from './test-utils';
test('exibe informações do usuário logado', () => {
render(<Perfil />);
expect(screen.getByText('Bem-vindo, Admin')).toBeInTheDocument();
});
7. Depuração e Boas Práticas em Testes de Integração
Quando um teste falha, use screen.debug() para inspecionar o DOM renderizado:
test('debugando', () => {
render(<Componente />);
screen.debug(); // Mostra o HTML completo
});
Use logRoles para listar os papéis acessíveis no componente:
import { logRoles } from '@testing-library/dom';
const { container } = render(<Componente />);
logRoles(container);
Estruture seus testes com describe e it para criar uma narrativa clara:
describe('Formulário de cadastro', () => {
it('exibe mensagem de erro quando email é inválido', async () => {
// ...
});
it('envia dados corretamente quando campos são válidos', async () => {
// ...
});
});
Lembre-se: teste comportamento, não implementação. Evite testar estados internos ou métodos privados. Foque no que o usuário vê e faz.
8. Integração com Ferramentas de CI e Cobertura
Para executar testes em pipelines CI (GitHub Actions, GitLab CI), adicione ao seu package.json:
{
"scripts": {
"test": "jest --coverage"
}
}
No GitHub Actions, um workflow básico seria:
name: Testes
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- run: npm ci
- run: npm test
O relatório de cobertura (--coverage) gera uma pasta coverage/ com dados do Istanbul. Configure thresholds no jest.config.js:
module.exports = {
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80,
},
},
};
Para manter testes rápidos, evite mockar bibliotecas desnecessárias e use jest.useFakeTimers() quando apropriado. Testes de integração bem escritos são a base para uma aplicação React confiável e de fácil manutenção.
Referências
- Documentação oficial da Testing Library — Guia completo sobre as bibliotecas de teste para React, incluindo queries, eventos e boas práticas.
- Testing Library: Guia de Queries — Explicação detalhada sobre os tipos de queries (getBy, findBy, queryBy) e como priorizar acessibilidade.
- Mock Service Worker (MSW) - Documentação — Tutorial oficial sobre como interceptar requisições HTTP em testes com MSW, ideal para testar chamadas de API.
- React Testing Library: Testes de Integração — Artigo técnico de Robin Wieruch com exemplos práticos de testes de integração em React.
- Jest: Configuração e Cobertura de Código — Documentação oficial do Jest sobre configuração de ambiente, mocks e geração de relatórios de cobertura.
- Testes no React com Jest e Testing Library — Tutorial no Dev.to com exemplos práticos de configuração e testes de componentes React.