Mocking de API em testes frontend
1. Por que mockar APIs em testes frontend?
Testar componentes React que consomem APIs externas apresenta desafios significativos. Dependências de rede tornam os testes lentos, instáveis e difíceis de reproduzir. Quando um teste falha, você nunca tem certeza se o problema está no seu código ou no servidor remoto.
Mockar APIs resolve esses problemas ao:
- Isolar o frontend de servidores, bancos de dados e serviços externos
- Garantir velocidade e confiabilidade — testes que dependem de rede podem levar segundos e falhar intermitentemente
- Simular cenários extremos que seriam difíceis de reproduzir com APIs reais: erros 500, timeouts, respostas lentas, payloads malformados
// Exemplo de teste sem mock — frágil e lento
test('carrega usuários da API real', async () => {
const response = await fetch('https://api.exemplo.com/users');
const data = await response.json();
expect(data.length).toBeGreaterThan(0); // Falha se API estiver fora do ar
});
2. Abordagens de mocking: stub, spy e fake
Existem três estratégias principais para simular dependências externas:
Stub: Substitui uma função por uma versão que retorna respostas fixas e previsíveis.
// Stub simples com Jest
jest.spyOn(global, 'fetch').mockResolvedValue({
json: async () => ({ id: 1, name: 'João' })
});
Spy: Monitora chamadas e argumentos sem necessariamente alterar o comportamento real.
const fetchSpy = jest.spyOn(window, 'fetch');
render(<MeuComponente />);
expect(fetchSpy).toHaveBeenCalledWith('/api/users');
Fake: Implementação simplificada de um serviço completo, como um servidor HTTP local.
3. Mocking no nível de fetch/axios com bibliotecas
MSW (Mock Service Worker)
O MSW intercepta requisições reais usando Service Worker no navegador ou por interceptação de rede no Node.js. É a abordagem mais próxima de um teste realista.
// handlers.js
import { http, HttpResponse } from 'msw';
export const handlers = [
http.get('/api/users', () => {
return HttpResponse.json([
{ id: 1, name: 'Ana' },
{ id: 2, name: 'Carlos' }
]);
}),
http.get('/api/users/:id', ({ params }) => {
return HttpResponse.json({ id: Number(params.id), name: 'Usuário' });
}),
http.post('/api/users', async ({ request }) => {
const body = await request.json();
return HttpResponse.json({ id: 3, ...body }, { status: 201 });
})
];
Nock
Ideal para testes de integração no Node.js, intercepta requisições HTTP reais.
const nock = require('nock');
nock('https://api.exemplo.com')
.get('/users')
.reply(200, [{ id: 1, name: 'Maria' }]);
Jest mocks manuais
Útil para mocking rápido de módulos específicos.
jest.mock('../services/api', () => ({
fetchUsers: jest.fn().mockResolvedValue([{ id: 1, name: 'Pedro' }])
}));
4. Prática com React Testing Library + MSW
Configuração do servidor MSW
// src/mocks/server.js
import { setupServer } from 'msw/node';
import { handlers } from './handlers';
export const server = setupServer(...handlers);
// src/setupTests.js
import { server } from './mocks/server';
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
Testando componentes com diferentes estados
// UserList.jsx
function UserList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch('/api/users')
.then(res => {
if (!res.ok) throw new Error('Erro na requisição');
return res.json();
})
.then(data => { setUsers(data); setLoading(false); })
.catch(err => { setError(err.message); setLoading(false); });
}, []);
if (loading) return <div data-testid="loading">Carregando...</div>;
if (error) return <div data-testid="error">{error}</div>;
return (
<ul>
{users.map(user => (
<li key={user.id} data-testid="user-item">{user.name}</li>
))}
</ul>
);
}
// UserList.test.js
import { render, screen, waitFor } from '@testing-library/react';
import { http, HttpResponse } from 'msw';
import { server } from './mocks/server';
import UserList from './UserList';
test('exibe lista de usuários após carregamento', async () => {
render(<UserList />);
expect(screen.getByTestId('loading')).toBeInTheDocument();
await waitFor(() => {
expect(screen.getByText('Ana')).toBeInTheDocument();
expect(screen.getByText('Carlos')).toBeInTheDocument();
});
});
test('exibe mensagem de erro quando API falha', async () => {
server.use(
http.get('/api/users', () => {
return HttpResponse.json({ message: 'Internal Server Error' }, { status: 500 });
})
);
render(<UserList />);
await waitFor(() => {
expect(screen.getByTestId('error')).toHaveTextContent('Erro na requisição');
});
});
5. Testando estados de UI com mocks
Simulação de carregamento com delay
test('mostra loading por tempo suficiente', async () => {
server.use(
http.get('/api/users', async () => {
await new Promise(resolve => setTimeout(resolve, 2000));
return HttpResponse.json([{ id: 1, name: 'Lento' }]);
})
);
render(<UserList />);
expect(screen.getByTestId('loading')).toBeInTheDocument();
});
Simulação de diferentes erros HTTP
const errorScenarios = [
{ status: 401, message: 'Não autorizado' },
{ status: 404, message: 'Não encontrado' },
{ status: 500, message: 'Erro interno' }
];
errorScenarios.forEach(({ status, message }) => {
test(`trata erro ${status}`, async () => {
server.use(
http.get('/api/users', () => {
return HttpResponse.json({ message }, { status });
})
);
render(<UserList />);
await waitFor(() => {
expect(screen.getByTestId('error')).toBeInTheDocument();
});
});
});
Resposta vazia ou parcial
test('lida com lista vazia', async () => {
server.use(
http.get('/api/users', () => {
return HttpResponse.json([]);
})
);
render(<UserList />);
await waitFor(() => {
expect(screen.queryByTestId('user-item')).not.toBeInTheDocument();
});
});
6. Boas práticas e armadilhas comuns
Evite mocks frágeis
Não acople seus testes a detalhes internos da implementação. Teste o comportamento observável pelo usuário, não chamadas de função específicas.
// ❌ Ruim: testa detalhe interno
expect(fetchMock).toHaveBeenCalledWith('/api/users', { method: 'GET' });
// ✅ Bom: testa comportamento visível
expect(screen.getByText('Ana')).toBeInTheDocument();
Mantenha mocks atualizados
Use contratos de API (OpenAPI/Swagger) para gerar automaticamente handlers do MSW e evitar divergências.
Limpeza entre testes
afterEach(() => {
server.resetHandlers(); // Remove handlers personalizados
jest.clearAllMocks(); // Limpa spies e stubs
});
7. Mocking em testes de integração vs. E2E
| Tipo de Teste | O que mockar | Quando usar |
|---|---|---|
| Unitário | Tudo externo | Testar lógica isolada do componente |
| Integração | API externa, mas testa fluxo interno | Validar interação entre componentes |
| E2E | Nada (API real) | Validar fluxo completo do usuário |
Estratégia híbrida: Use mocks para testes de unidade/integração (rápidos e controlados) e complemente com testes E2E em ambiente de staging para cenários críticos.
8. Ferramentas complementares e próximos passos
- json-server: Crie uma API REST fake em segundos para desenvolvimento local
- Faker.js: Gere dados realistas (nomes, emails, endereços) para seus mocks
- Cypress: Oferece interceptação de rede nativa para testes E2E com mocks
// Exemplo de integração com CI/CD
// .github/workflows/test.yml
name: Testes
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: npm ci
- run: npm test -- --coverage # MSW roda em background automaticamente
O mocking de API é uma habilidade essencial para construir testes frontend robustos e confiáveis. Com ferramentas como MSW e React Testing Library, você pode simular qualquer cenário de forma previsível e rápida, garantindo que seu componente se comporte corretamente em todas as situações.
Referências
- MSW - Mock Service Worker Documentation — Documentação oficial do MSW com guias de configuração e exemplos práticos para REST e GraphQL
- React Testing Library - Testing Async Operations — Guia oficial sobre como testar operações assíncronas e chamadas de API em componentes React
- Jest - Mock Functions — Documentação oficial do Jest sobre criação de mocks, spies e stubs para testes unitários
- Nock - HTTP mocking library — Repositório oficial do Nock para interceptação de requisições HTTP em Node.js
- Kent C. Dodds - Stop mocking fetch — Artigo técnico que explica por que e como usar MSW em vez de mocks manuais de fetch
- Testing Library - Common Mistakes with React Testing Library — Boas práticas para evitar armadilhas comuns ao testar componentes React com mocks de API