Fundamentos de Testes em React com Testing Library
A Testing Library é uma biblioteca que enfatiza testes centrados no usuário, não na implementação. Ao contrário do Enzyme, que permite acessar estados internos, a Testing Library força você a testar como um usuário realmente interage com sua aplicação. Isso significa buscar elementos pelo texto visível, labels, placeholders e roles ARIA — nunca por seletores de classe ou ID diretos.
O primeiro passo é entender a hierarquia de queries. Existem três categorias: getBy (lança erro se não encontrar), queryBy (retorna null) e findBy (assíncrono, ideal para elementos que aparecem após renderização). Para começar, instale as dependências:
npm install --save-dev @testing-library/react @testing-library/jest-dom vitest
Aqui está um exemplo funcional de um teste básico:
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { LoginForm } from './LoginForm';
describe('LoginForm', () => {
it('deve exibir mensagem de sucesso ao enviar', async () => {
render(<LoginForm />);
const emailInput = screen.getByLabelText(/email/i);
const submitButton = screen.getByRole('button', { name: /entrar/i });
await userEvent.type(emailInput, 'user@example.com');
await userEvent.click(submitButton);
const successMessage = await screen.findByText(/bem-vindo/i);
expect(successMessage).toBeInTheDocument();
});
});
Mock de APIs com MSW (Mock Service Worker)
MSW intercepta requisições HTTP no nível da rede, sem alterar seu código. Funciona tanto em testes quanto no desenvolvimento real. Configure handlers que definem como a rede deve responder:
npm install --save-dev msw
Crie um arquivo de configuração para seus handlers:
// src/mocks/handlers.js
import { http, HttpResponse } from 'msw';
export const handlers = [
http.get('https://api.example.com/users/:id', ({ params }) => {
return HttpResponse.json({
id: params.id,
name: 'João Silva',
email: 'joao@example.com'
});
}),
http.post('https://api.example.com/login', async ({ request }) => {
const body = await request.json();
if (body.password === 'correct') {
return HttpResponse.json({ token: 'abc123' }, { status: 200 });
}
return HttpResponse.json({ error: 'Inválido' }, { status: 401 });
})
];
Configure o servidor MSW no seu setup de testes:
// src/mocks/server.js
import { setupServer } from 'msw/node';
import { handlers } from './handlers';
export const server = setupServer(...handlers);
No arquivo de configuração do seu test runner (vitest.config.js):
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'jsdom',
setupFiles: './src/mocks/setupTests.js'
}
});
E em setupTests.js:
import { beforeAll, afterEach, afterAll } from 'vitest';
import { server } from './mocks/server';
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
Estratégias Avançadas de Mock
Existem situações onde você precisa sobrescrever handlers padrão ou mockar de forma granular. Use server.use() para injetar handlers específicos em testes individuais:
import { http, HttpResponse } from 'msw';
import { server } from './mocks/server';
describe('UserProfile', () => {
it('deve exibir erro quando API falha', async () => {
// Sobrescreve o handler padrão
server.use(
http.get('https://api.example.com/users/:id', () => {
return HttpResponse.json(
{ error: 'Não encontrado' },
{ status: 404 }
);
})
);
render(<UserProfile userId="1" />);
const errorMessage = await screen.findByText(/não encontrado/i);
expect(errorMessage).toBeInTheDocument();
});
});
Para dados complexos ou múltiplas requisições, considere usar factory functions:
// src/mocks/factories.js
export function createUser(overrides = {}) {
return {
id: '1',
name: 'Padrão',
email: 'padrao@example.com',
role: 'user',
...overrides
};
}
export function createApiError(status = 500, message = 'Erro') {
return { status, message };
}
Outro padrão importante é mockar módulos locais. Use vi.mock() para casos onde você precisa controlar comportamentos de utilidades ou serviços:
import { vi } from 'vitest';
import { calculateDiscount } from './utils';
vi.mock('./utils', () => ({
calculateDiscount: vi.fn(() => 0.20)
}));
it('aplica desconto correto', () => {
expect(calculateDiscount(100)).toBe(0.20);
});
Boas Práticas e Anti-padrões
Teste comportamentos, não implementações. Evite testar estado interno ou métodos privados — isso torna testes frágeis e acoplados. Quando um teste quebra porque você refatorou componentes internos (mantendo a funcionalidade), isso sinaliza um teste mal escrito.
Sempre use screen em vez de render().container. A Testing Library remove acesso direto ao DOM exatamente para encorajar consultas acessíveis. Nunca faça:
// ❌ Evite
const { container } = render(<MyComponent />);
container.querySelector('.my-class').textContent;
// ✅ Faça
screen.getByRole('heading', { name: /meu título/i });
Aguarde elementos assincronamente com findBy ao invés de getBy para requisições:
// ✅ Correto para dados de API
const user = await screen.findByText('João Silva');
// ❌ Errado — pode falhar em testes rápidos
const user = screen.getByText('João Silva');
Conclusão
Testes em React com Testing Library, MSW e estratégias de mock consolidam três pilares: 1) Testing Library força testes centrados no usuário, eliminando testes frágeis acoplados à implementação; 2) MSW fornece interceptação de rede real sem poluir seu código, sendo reutilizável entre testes e desenvolvimento; 3) Mocking granular com server.use() e vi.mock() oferece controle fino sem sacrificar clareza. Domine esses fundamentos e seus testes se tornarão uma segurança real, não apenas cobertura numérica.