<h2>O que é MSW (Mock Service Worker)?</h2>
<p>Mock Service Worker é uma biblioteca JavaScript que intercepta requisições HTTP em nível de rede, permitindo que você simule respostas de APIs sem modificar o código da sua aplicação. Diferente de outras abordagens, o MSW funciona com Service Workers no navegador e com adapters para Node.js, oferecendo uma solução agnóstica de framework.</p>
<p>A grande vantagem do MSW é que ele não mocka bibliotecas HTTP específicas — como axios ou fetch — mas sim intercepta as requisições no ponto mais baixo possível. Isso significa que qualquer código que faça uma chamada HTTP funcionará com seus mocks, tornando os testes mais próximos do comportamento real da aplicação.</p>
<h2>Instalação e Configuração Inicial</h2>
<p>Para começar, você precisa instalar o MSW e gerar os arquivos necessários:</p>
<pre><code class="language-bash">npm install msw --save-dev
npx msw init public/</code></pre>
<p>O comando <code>msw init</code> cria um arquivo Service Worker no diretório <code>public/</code> da sua aplicação. Este arquivo é essencial para interceptar requisições no navegador durante o desenvolvimento e testes.</p>
<h3>Estrutura de um Handler MSW</h3>
<p>Um handler no MSW define qual requisição será interceptada e qual será a resposta. Vamos criar nosso primeiro arquivo de handlers:</p>
<pre><code class="language-javascript">// src/mocks/handlers.js
import { http, HttpResponse } from 'msw';
export const handlers = [
// GET com resposta bem-sucedida
http.get('/api/users', () => {
return HttpResponse.json(
[
{ id: 1, name: 'João Silva', email: 'joao@example.com' },
{ id: 2, name: 'Maria Santos', email: 'maria@example.com' }
],
{ status: 200 }
);
}),
// POST com validação
http.post('/api/users', async ({ request }) => {
const body = await request.json();
if (!body.name || !body.email) {
return HttpResponse.json(
{ error: 'Nome e email são obrigatórios' },
{ status: 400 }
);
}
return HttpResponse.json(
{ id: 3, ...body, createdAt: new Date().toISOString() },
{ status: 201 }
);
}),
// Erro simulado
http.get('/api/profile/:id', ({ params }) => {
if (params.id === '999') {
return HttpResponse.json(
{ error: 'Usuário não encontrado' },
{ status: 404 }
);
}
return HttpResponse.json({
id: params.id,
name: 'João Silva',
role: 'Developer'
});
})
];</code></pre>
<h3>Configurando o Servidor MSW para Testes</h3>
<p>Para usar MSW nos testes com Jest ou Vitest, crie um arquivo de configuração:</p>
<pre><code class="language-javascript">// src/mocks/server.js
import { setupServer } from 'msw/node';
import { handlers } from './handlers';
export const server = setupServer(...handlers);</code></pre>
<p>Em seguida, configure seu arquivo de setup dos testes:</p>
<pre><code class="language-javascript">// src/setupTests.js
import { server } from './mocks/server';
// Inicia o servidor antes de todos os testes
beforeAll(() => server.listen());
// Reseta os handlers após cada teste
afterEach(() => server.resetHandlers());
// Encerra o servidor após todos os testes
afterAll(() => server.close());</code></pre>
<h2>Testes com React e MSW</h2>
<h3>Testando Componentes que Fazem Requisições</h3>
<p>Vamos criar um componente React que busca dados e testá-lo com MSW:</p>
<pre><code class="language-jsx">// src/components/UserList.jsx
import { useState, useEffect } from 'react';
export function UserList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch('/api/users')
.then(res => res.json())
.then(data => {
setUsers(data);
setLoading(false);
})
.catch(err => {
setError(err.message);
setLoading(false);
});
}, []);
if (loading) return <div>Carregando...</div>;
if (error) return <div>Erro: {error}</div>;
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name} - {user.email}</li>
))}
</ul>
);
}</code></pre>
<p>Agora o teste:</p>
<pre><code class="language-javascript">// src/components/UserList.test.jsx
import { render, screen, waitFor } from '@testing-library/react';
import { UserList } from './UserList';
import { server } from '../mocks/server';
import { http, HttpResponse } from 'msw';
describe('UserList', () => {
test('renderiza lista de usuários com sucesso', async () => {
render(<UserList />);
expect(screen.getByText('Carregando...')).toBeInTheDocument();
await waitFor(() => {
expect(screen.getByText('João Silva - joao@example.com')).toBeInTheDocument();
expect(screen.getByText('Maria Santos - maria@example.com')).toBeInTheDocument();
});
});
test('exibe mensagem de erro quando a API falha', async () => {
// Sobrescreve o handler para este teste específico
server.use(
http.get('/api/users', () => {
return HttpResponse.json(
{ error: 'Erro interno do servidor' },
{ status: 500 }
);
})
);
render(<UserList />);
await waitFor(() => {
expect(screen.getByText(/Erro:/i)).toBeInTheDocument();
});
});
});</code></pre>
<h3>Testando Formulários com Requisições POST</h3>
<pre><code class="language-jsx">// src/components/CreateUser.jsx
import { useState } from 'react';
export function CreateUser({ onUserCreated }) {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const handleSubmit = async (e) => {
e.preventDefault();
setLoading(true);
setError(null);
try {
const res = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, email })
});
if (!res.ok) {
const data = await res.json();
throw new Error(data.error);
}
const newUser = await res.json();
onUserCreated(newUser);
setName('');
setEmail('');
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Nome"
required
/>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
required
/>
<button type="submit" disabled={loading}>
{loading ? 'Criando...' : 'Criar Usuário'}
</button>
{error && <span className="error">{error}</span>}
</form>
);
}</code></pre>
<p>Teste correspondente:</p>
<pre><code class="language-javascript">// src/components/CreateUser.test.jsx
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { CreateUser } from './CreateUser';
import { server } from '../mocks/server';
describe('CreateUser', () => {
test('cria um usuário com sucesso', async () => {
const mockCallback = jest.fn();
render(<CreateUser onUserCreated={mockCallback} />);
fireEvent.change(screen.getByPlaceholderText('Nome'), {
target: { value: 'Pedro Costa' }
});
fireEvent.change(screen.getByPlaceholderText('Email'), {
target: { value: 'pedro@example.com' }
});
fireEvent.click(screen.getByRole('button', { name: /Criar Usuário/i }));
await waitFor(() => {
expect(mockCallback).toHaveBeenCalledWith(
expect.objectContaining({
name: 'Pedro Costa',
email: 'pedro@example.com'
})
);
});
});
test('exibe erro de validação quando faltam campos', async () => {
const mockCallback = jest.fn();
render(<CreateUser onUserCreated={mockCallback} />);
fireEvent.change(screen.getByPlaceholderText('Nome'), {
target: { value: 'Pedro Costa' }
});
fireEvent.click(screen.getByRole('button'));
await waitFor(() => {
expect(screen.getByText(/obrigatórios/i)).toBeInTheDocument();
expect(mockCallback).not.toHaveBeenCalled();
});
});
});</code></pre>
<h2>Desenvolvimento Local com MSW</h2>
<h3>Usando MSW no Navegador Durante o Desenvolvimento</h3>
<p>Para usar MSW enquanto desenvolve localmente, adicione o seguinte ao seu arquivo principal:</p>
<pre><code class="language-javascript">// src/main.jsx (Vite) ou src/index.js (Create React App)
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
async function enableMocking() {
if (process.env.NODE_ENV === 'development') {
const { worker } = await import('./mocks/browser');
return worker.start();
}
}
enableMocking().then(() => {
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>
);
});</code></pre>
<p>Crie um arquivo para exportar o worker do navegador:</p>
<pre><code class="language-javascript">// src/mocks/browser.js
import { setupWorker } from 'msw/browser';
import { handlers } from './handlers';
export const worker = setupWorker(...handlers);</code></pre>
<p>Agora, quando você iniciar a aplicação, o MSW interceptará todas as requisições, permitindo que você trabalhe sem uma API real.</p>
<h3>Simulando Comportamentos Realistas</h3>
<p>Para testes mais robustos, simule delays de rede e cenários realistas:</p>
<pre><code class="language-javascript">// src/mocks/handlers.js (adição)
import { http, HttpResponse, delay } from 'msw';
export const handlersWithDelay = [
http.get('/api/users', async () => {
await delay(800); // Simula latência de rede
return HttpResponse.json([
{ id: 1, name: 'João Silva', email: 'joao@example.com' }
]);
}),
http.post('/api/payment', async ({ request }) => {
await delay(2000); // Simula processamento lento
const body = await request.json();
if (body.cardToken === 'INVALID') {
return HttpResponse.json(
{ error: 'Cartão inválido' },
{ status: 400 }
);
}
return HttpResponse.json({
transactionId: Math.random().toString(36).substring(7),
status: 'approved',
amount: body.amount
});
})
];</code></pre>
<h2>Conclusão</h2>
<p>Você aprendeu que <strong>MSW é uma solução poderosa e agnóstica que intercepta requisições em nível de rede</strong>, funcionando tanto no navegador quanto em testes Node.js. Isso torna seus testes mais robustos e realistas, já que o código da sua aplicação não precisa saber que está fazendo requisições mockadas.</p>
<p>A segunda lição importante é que <strong>MSW permite sobrescrever handlers por teste</strong>, oferecendo granularidade máxima para simular diferentes cenários sem contaminar outros testes. Você viu como isso funciona ao usar <code>server.use()</code> para definir comportamentos específicos.</p>
<p>Por fim, entenda que <strong>MSW reduz a complexidade de desenvolvimento e testes</strong>, eliminando a necessidade de manter fixtures de dados em múltiplos lugares ou modificar sua lógica de requisições. Você trabalha com uma API consistente que funciona tanto durante o desenvolvimento quanto nos testes automatizados.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://mswjs.io" target="_blank" rel="noopener noreferrer">Documentação Oficial do MSW</a></li>
<li><a href="https://mswjs.io/docs/integrations/react" target="_blank" rel="noopener noreferrer">MSW com React Testing Library - Guia Oficial</a></li>
<li><a href="https://testing-library.com/docs/react-testing-library/intro/" target="_blank" rel="noopener noreferrer">Testing Library - Best Practices</a></li>
<li><a href="https://kentcdodds.com/blog/stop-mocking-fetch" target="_blank" rel="noopener noreferrer">Artigo: Mocking APIs com MSW em React</a></li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API" target="_blank" rel="noopener noreferrer">Service Workers MDN - Conceitos Fundamentais</a></li>
</ul>
<p><!-- FIM --></p>