React & Frontend

MSW em React: Mock Service Worker para Testes e Desenvolvimento na Prática

11 min de leitura

MSW em React: Mock Service Worker para Testes e Desenvolvimento na Prática

O que é MSW (Mock Service Worker)? 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. 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. Instalação e Configuração Inicial Para começar, você precisa instalar o MSW e gerar os arquivos necessários: O comando cria um arquivo Service Worker no diretório da sua aplicação. Este arquivo é essencial para interceptar requisições no navegador durante o desenvolvimento e testes. Estrutura de um Handler MSW Um

<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 &#039;msw&#039;;

export const handlers = [

// GET com resposta bem-sucedida

http.get(&#039;/api/users&#039;, () =&gt; {

return HttpResponse.json(

[

{ id: 1, name: &#039;João Silva&#039;, email: &#039;joao@example.com&#039; },

{ id: 2, name: &#039;Maria Santos&#039;, email: &#039;maria@example.com&#039; }

],

{ status: 200 }

);

}),

// POST com validação

http.post(&#039;/api/users&#039;, async ({ request }) =&gt; {

const body = await request.json();

if (!body.name || !body.email) {

return HttpResponse.json(

{ error: &#039;Nome e email são obrigatórios&#039; },

{ status: 400 }

);

}

return HttpResponse.json(

{ id: 3, ...body, createdAt: new Date().toISOString() },

{ status: 201 }

);

}),

// Erro simulado

http.get(&#039;/api/profile/:id&#039;, ({ params }) =&gt; {

if (params.id === &#039;999&#039;) {

return HttpResponse.json(

{ error: &#039;Usuário não encontrado&#039; },

{ status: 404 }

);

}

return HttpResponse.json({

id: params.id,

name: &#039;João Silva&#039;,

role: &#039;Developer&#039;

});

})

];</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 &#039;msw/node&#039;;

import { handlers } from &#039;./handlers&#039;;

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 &#039;./mocks/server&#039;;

// Inicia o servidor antes de todos os testes

beforeAll(() =&gt; server.listen());

// Reseta os handlers após cada teste

afterEach(() =&gt; server.resetHandlers());

// Encerra o servidor após todos os testes

afterAll(() =&gt; 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 &#039;react&#039;;

export function UserList() {

const [users, setUsers] = useState([]);

const [loading, setLoading] = useState(true);

const [error, setError] = useState(null);

useEffect(() =&gt; {

fetch(&#039;/api/users&#039;)

.then(res =&gt; res.json())

.then(data =&gt; {

setUsers(data);

setLoading(false);

})

.catch(err =&gt; {

setError(err.message);

setLoading(false);

});

}, []);

if (loading) return &lt;div&gt;Carregando...&lt;/div&gt;;

if (error) return &lt;div&gt;Erro: {error}&lt;/div&gt;;

return (

&lt;ul&gt;

{users.map(user =&gt; (

&lt;li key={user.id}&gt;{user.name} - {user.email}&lt;/li&gt;

))}

&lt;/ul&gt;

);

}</code></pre>

<p>Agora o teste:</p>

<pre><code class="language-javascript">// src/components/UserList.test.jsx

import { render, screen, waitFor } from &#039;@testing-library/react&#039;;

import { UserList } from &#039;./UserList&#039;;

import { server } from &#039;../mocks/server&#039;;

import { http, HttpResponse } from &#039;msw&#039;;

describe(&#039;UserList&#039;, () =&gt; {

test(&#039;renderiza lista de usuários com sucesso&#039;, async () =&gt; {

render(&lt;UserList /&gt;);

expect(screen.getByText(&#039;Carregando...&#039;)).toBeInTheDocument();

await waitFor(() =&gt; {

expect(screen.getByText(&#039;João Silva - joao@example.com&#039;)).toBeInTheDocument();

expect(screen.getByText(&#039;Maria Santos - maria@example.com&#039;)).toBeInTheDocument();

});

});

test(&#039;exibe mensagem de erro quando a API falha&#039;, async () =&gt; {

// Sobrescreve o handler para este teste específico

server.use(

http.get(&#039;/api/users&#039;, () =&gt; {

return HttpResponse.json(

{ error: &#039;Erro interno do servidor&#039; },

{ status: 500 }

);

})

);

render(&lt;UserList /&gt;);

await waitFor(() =&gt; {

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 &#039;react&#039;;

export function CreateUser({ onUserCreated }) {

const [name, setName] = useState(&#039;&#039;);

const [email, setEmail] = useState(&#039;&#039;);

const [loading, setLoading] = useState(false);

const [error, setError] = useState(null);

const handleSubmit = async (e) =&gt; {

e.preventDefault();

setLoading(true);

setError(null);

try {

const res = await fetch(&#039;/api/users&#039;, {

method: &#039;POST&#039;,

headers: { &#039;Content-Type&#039;: &#039;application/json&#039; },

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(&#039;&#039;);

setEmail(&#039;&#039;);

} catch (err) {

setError(err.message);

} finally {

setLoading(false);

}

};

return (

&lt;form onSubmit={handleSubmit}&gt;

&lt;input

type=&quot;text&quot;

value={name}

onChange={(e) =&gt; setName(e.target.value)}

placeholder=&quot;Nome&quot;

required

/&gt;

&lt;input

type=&quot;email&quot;

value={email}

onChange={(e) =&gt; setEmail(e.target.value)}

placeholder=&quot;Email&quot;

required

/&gt;

&lt;button type=&quot;submit&quot; disabled={loading}&gt;

{loading ? &#039;Criando...&#039; : &#039;Criar Usuário&#039;}

&lt;/button&gt;

{error &amp;&amp; &lt;span className=&quot;error&quot;&gt;{error}&lt;/span&gt;}

&lt;/form&gt;

);

}</code></pre>

<p>Teste correspondente:</p>

<pre><code class="language-javascript">// src/components/CreateUser.test.jsx

import { render, screen, fireEvent, waitFor } from &#039;@testing-library/react&#039;;

import { CreateUser } from &#039;./CreateUser&#039;;

import { server } from &#039;../mocks/server&#039;;

describe(&#039;CreateUser&#039;, () =&gt; {

test(&#039;cria um usuário com sucesso&#039;, async () =&gt; {

const mockCallback = jest.fn();

render(&lt;CreateUser onUserCreated={mockCallback} /&gt;);

fireEvent.change(screen.getByPlaceholderText(&#039;Nome&#039;), {

target: { value: &#039;Pedro Costa&#039; }

});

fireEvent.change(screen.getByPlaceholderText(&#039;Email&#039;), {

target: { value: &#039;pedro@example.com&#039; }

});

fireEvent.click(screen.getByRole(&#039;button&#039;, { name: /Criar Usuário/i }));

await waitFor(() =&gt; {

expect(mockCallback).toHaveBeenCalledWith(

expect.objectContaining({

name: &#039;Pedro Costa&#039;,

email: &#039;pedro@example.com&#039;

})

);

});

});

test(&#039;exibe erro de validação quando faltam campos&#039;, async () =&gt; {

const mockCallback = jest.fn();

render(&lt;CreateUser onUserCreated={mockCallback} /&gt;);

fireEvent.change(screen.getByPlaceholderText(&#039;Nome&#039;), {

target: { value: &#039;Pedro Costa&#039; }

});

fireEvent.click(screen.getByRole(&#039;button&#039;));

await waitFor(() =&gt; {

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 &#039;react&#039;;

import ReactDOM from &#039;react-dom/client&#039;;

import App from &#039;./App&#039;;

async function enableMocking() {

if (process.env.NODE_ENV === &#039;development&#039;) {

const { worker } = await import(&#039;./mocks/browser&#039;);

return worker.start();

}

}

enableMocking().then(() =&gt; {

ReactDOM.createRoot(document.getElementById(&#039;root&#039;)).render(

&lt;React.StrictMode&gt;

&lt;App /&gt;

&lt;/React.StrictMode&gt;

);

});</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 &#039;msw/browser&#039;;

import { handlers } from &#039;./handlers&#039;;

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 &#039;msw&#039;;

export const handlersWithDelay = [

http.get(&#039;/api/users&#039;, async () =&gt; {

await delay(800); // Simula latência de rede

return HttpResponse.json([

{ id: 1, name: &#039;João Silva&#039;, email: &#039;joao@example.com&#039; }

]);

}),

http.post(&#039;/api/payment&#039;, async ({ request }) =&gt; {

await delay(2000); // Simula processamento lento

const body = await request.json();

if (body.cardToken === &#039;INVALID&#039;) {

return HttpResponse.json(

{ error: &#039;Cartão inválido&#039; },

{ status: 400 }

);

}

return HttpResponse.json({

transactionId: Math.random().toString(36).substring(7),

status: &#039;approved&#039;,

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>&lt;!-- FIM --&gt;</p>

Comentários

Mais em React & Frontend

Como Usar Micro-frontends com React: Module Federation e Arquitetura Distribuída em Produção
Como Usar Micro-frontends com React: Module Federation e Arquitetura Distribuída em Produção

O que são Micro-frontends e Por Que Module Federation? Micro-frontends repres...

Como Usar Web Vitals em Aplicações React: LCP, CLS, INP e Otimizações em Produção
Como Usar Web Vitals em Aplicações React: LCP, CLS, INP e Otimizações em Produção

Web Vitals em Aplicações React: Entendendo as Métricas Essenciais Os Web Vita...

O que Todo Dev Deve Saber sobre Hooks para Formulários: Abstraindo Validação e Estado de Campos
O que Todo Dev Deve Saber sobre Hooks para Formulários: Abstraindo Validação e Estado de Campos

Entendendo o Problema: Estado e Validação em Formulários Quando começamos a t...