Mockando navegações de página e formulários com o MSW 3
Simule navegações e envios de formulários nativos com MSW 3 e handlers http. Veja respostas HTML, leitura com formData e mudanças importantes na migração.
O Mock Service Worker (MSW) 3.0.0 foi lançado em 28 de setembro de 2026 e adicionou suporte à interceptação de navegações de página e envios de formulário (#2721). Isso significa que um clique em um link ou o envio de um formulário HTML simples agora pode receber uma resposta mockada com os mesmos handlers http que você já escreve.
Até esta versão, o envio nativo de um formulário em um setup com Storybook ou Playwright passava completamente batido pelo MSW. Ou você precisava de um backend real rodando só para aquele fluxo, ou simplesmente pulava o teste. Em uma discussão de 2023, o mantenedor explicou que o MSW ignorava requisições de navegação de propósito, para evitar que um mock fosse servido por engano no lugar de uma página HTML real.
Este artigo aborda o que a mudança torna testável, as breaking changes da v3 com que você vai se deparar primeiro e padrões de handlers para páginas e envios de formulário. A versão de correção 3.0.3 saiu em 10 de outubro de 2026. Para instalar o MSW e criar seus primeiros handlers, consulte mocking API servers with Mock Service Worker.
Principais pontos
- O MSW 3.0.0 (28 de setembro de 2026) adiciona a interceptação de navegações de página e envios de formulário, mas não há uma API dedicada documentada para isso, então você usa os handlers padrão
http.getehttp.post. - Um formulário HTML simples envia seus campos codificados como URL (URL-encoded), e não como JSON, então leia o corpo com
request.formData()em vez derequest.json(). - O MSW 3 é exclusivamente ESM e exige Node.js 22 ou superior e TypeScript 5.9 ou superior.
onUnhandledRequestfoi renomeado paraonUnhandledFrame, erequest.headers.get('cookie')agora retornanulldentro dos handlers.
O que o mock de navegação e formulários do MSW 3 torna possível?
Com o MSW 3, você pode testar um formulário renderizado no servidor ou um link para outra página sem rodar um backend. O handler retorna o documento HTML da próxima página, e o navegador o renderiza como faria com uma resposta real do servidor.
Antes da versão 3, o MSW só cobria requisições feitas pelo seu JavaScript com fetch ou XHR. Isso deixava de fora um formulário de cadastro com progressive enhancement, um formulário de busca que recarrega a página e um link “ver detalhes” para uma página renderizada no servidor. Agora você pode descrever cada um deles como um handler e fazer asserções sobre o que o usuário efetivamente vê no final.
Quem mais se beneficia é o caminho de erro. Replays de sessão de fluxos com formulários frequentemente mostram usuários caindo em uma página em branco ou em uma página de erro crua depois de clicar em enviar. Um POST mockado que retorna HTML de erro de validação ou um 500 permite reproduzir essa tela em um teste antes do release.
Atualizando para o MSW 3: as breaking changes que aparecem primeiro
A maioria das suítes quebra primeiro por causa das versões mínimas de runtime e de uma única opção renomeada. Estas são as mudanças das release notes da v3.0.0 que você deve verificar antes de olhar os novos recursos:
- O MSW agora é exclusivamente ESM (#2765).
- A versão mínima suportada do Node.js é a v22 (#2729).
- O TypeScript 5.9 passa a ser a versão mínima.
graphqlagora é exportado comomsw/graphql(#2691), e o GraphQL agora é uma peer dependency opcional de fato. Os handlers de GraphQL também passam a ser definidos em umgraphql.link(), já quegraphql.query()egraphql.mutation()foram removidos do namespace.- A opção
onUnhandledRequestfoi renomeada paraonUnhandledFrame. worker.stop()retorna uma Promise (#2724). Você só precisa usarawaitnela em cenários críticos, com requisições ainda em andamento.msw/nativefoi removido em favor de@msw/react-native. Uma issue no GitHub relata que@msw/react-nativeainda não foi publicado no npm e que seu código-fonte ainda tem como alvo o MSW 2, então projetos React Native não têm um caminho de atualização funcional até que ele seja lançado.
As release notes e o guia de migração 2.x → 3.x cobrem todo o resto. Os snippets deste artigo usam o novo entrypoint msw/http. Segundo o anúncio do MSW 3.0, os exports na raiz de msw continuam existindo para manter a retrocompatibilidade, mas a próxima versão major vai remover tudo o que puder ser importado de um entrypoint específico.
Como mockar uma navegação de página com http.get?
Para mockar uma navegação de página, escreva um handler http.get para o path da página e retorne um Response com corpo HTML e content type text/html. As release notes da v3 anunciam a interceptação de navegação e de formulários, mas nem elas nem o guia de migração documentam uma API ou etapa de configuração dedicada. Por isso, os exemplos abaixo usam apenas os handlers padrão.
Cada request handler do MSW combina um predicado, que filtra as requisições recebidas, com um response resolver, que trata as que passam pelo filtro. A documentação de interceptação de requisições observa que predicados relativos, como /signup, são resolvidos em relação a location.href. Um resolver que retorna uma instância de Response faz dessa instância a resposta mockada, como descreve a documentação de tratamento de requisições. É por isso que um simples new Response funciona aqui, sem nenhum helper específico para HTML.
import { http } from 'msw/http'
export const html = (body: string, status = 200) =>
new Response(`<!doctype html><html><body>${body}</body></html>`, {
status,
headers: { 'Content-Type': 'text/html' },
})
export const pageHandlers = [
http.get('/signup', () =>
html(`<form method="post" action="/signup">
<input name="email" type="email">
<button>Sign up</button>
</form>`),
),
]
Formulários de busca que usam method="get" se comportam de outra forma. Um formulário enviado com method="get" coloca seus campos na query string. Predicados do MSW não devem conter query parameters, então faça o match apenas pelo path e leia os campos de new URL(request.url).searchParams:
http.get('/search', ({ request }) => {
const q = new URL(request.url).searchParams.get('q') ?? ''
const safe = q.replace(/&/g, '&').replace(/</g, '<')
return html(`<h1>Results for ${safe}</h1>`)
})
Como mockar o envio de um formulário com http.post?
O envio nativo de um formulário é tratado por um handler http.post para a URL do action do formulário. O resolver recebe request como um Request padrão da Fetch API, junto com params e cookies, e qualquer Response que ele retornar se torna a página que o navegador exibe em seguida.
A única diferença em relação ao padrão do MSW 2 que você já conhece é o formato do corpo. Quando o seu próprio JavaScript envia um formulário, normalmente ele manda JSON:
import { http, HttpResponse } from 'msw/http'
http.post<never, { email: string }>('/api/signup', async ({ request }) => {
const body = await request.clone().json()
return HttpResponse.json({ ok: true, email: body.email }, { status: 201 })
})
Um formulário HTML simples envia seus campos como dados de formulário codificados como URL, e não como JSON, porque application/x-www-form-urlencoded é a codificação padrão de formulários. Chamar .json() nesse corpo gera erro. Em vez disso, leia-o com Request.formData(). O guia de upload de arquivos do MSW mostra formData() sendo usado em requisições interceptadas.
import { http } from 'msw/http'
import { html } from './pages'
export const formHandlers = [
http.post('/signup', async ({ request }) => {
const form = await request.clone().formData()
const email = form.get('email')
if (!email) {
return html('<p role="alert">Email is required.</p>', 422)
}
return html('<h1>Check your inbox</h1>')
}),
]
Retorne um branch separado para cada resultado do servidor que você precisa testar: sucesso, falha de validação e uma página de erro 500. Assim, suas asserções cobrem o documento que o usuário realmente vê.
Fluxos com formulários frequentemente dependem de cookies de sessão. Na v3, request.headers.get('cookie') não retorna mais uma string de cookie mockada dentro dos handlers. Ele retorna null em todos os ambientes, então use o argumento cookies do resolver para qualquer lógica baseada em cookies.
Onde funciona a interceptação de navegação e formulários do MSW 3?
As release notes da v3 não informam quais ambientes suportam a interceptação de navegação e formulários, então confira o PR #2721 e a documentação em relação ao seu próprio setup antes de depender disso. Uma navegação de página é algo que o navegador executa, o que torna os runners baseados em navegador o lugar natural para testar esses handlers.
O histórico do MSW explica o porquê. Antes da versão 3, o script do worker ignorava requisições cujo mode era navigate, como o mantenedor explicou na Discussion #1578. Se a sua suíte com Vitest ou Jest roda testes de componente no Node, como em mocking API calls in Vue tests with Vitest, os mocks de fetch continuam funcionando como antes. Não presuma que o envio nativo de um formulário se comporta da mesma forma nesse ambiente. A versão também adiciona um plugin para o Vite, msw/vite (#2781), que serve o script do worker para você, então não é mais necessário copiar o mockServiceWorker.js para o seu diretório público.
Conclusão
O MSW 3 não exige nenhum vocabulário novo para mockar páginas e formulários. Você usa http.get para a página, http.post para o envio, formData() para o corpo e um Response com text/html para o resultado. Faça a atualização nesta ordem: migre para Node.js 22, TypeScript 5.9 e ESM, renomeie onUnhandledRequest para onUnhandledFrame e troque os imports para msw/http. Depois disso, adicione um handler para cada resultado do seu formulário mais importante, começando pela página de erro.
Perguntas frequentes
O request.formData() funciona com formulários que usam enctype multipart/form-data?
Sim. Request.formData() faz o parsing tanto de corpos application/x-www-form-urlencoded quanto multipart/form-data para um objeto FormData, então o mesmo código de handler funciona independentemente de o formulário incluir ou não um input de arquivo. Campos de texto são retornados como strings por form.get(), e inputs de arquivo são retornados como objetos File, cujos name, size e type você pode inspecionar.
Como leio grupos de checkboxes ou campos de seleção múltipla em um handler de formulário?
Use form.getAll('name') em vez de form.get('name'). Um grupo de checkboxes ou um select com o atributo multiple envia uma entrada para cada valor selecionado sob o mesmo nome de campo, e get() retorna apenas o primeiro. getAll() retorna um array com todos os valores, ou um array vazio quando nada está selecionado, já que o navegador simplesmente não envia checkboxes desmarcados.
Qual é a diferença entre retornar passthrough() e não retornar nada em um resolver do MSW?
Retornar passthrough(), que no MSW 3 é importado de msw/utils/passthrough, envia a requisição para a rede real sem alterações e a marca como tratada, de modo que nenhum handler posterior pode afetá-la. Não retornar nada deixa a requisição intocada, e o MSW tenta o próximo handler que corresponder a ela. Use passthrough() quando quiser, de propósito, que uma página ou um envio de formulário específico chegue a um servidor real.
Preciso regenerar o mockServiceWorker.js depois de atualizar para o MSW 3?
Sim, a menos que você use o novo plugin msw/vite, que serve o script do worker para você. Caso contrário, depois de instalar o MSW 3, rode npx msw init seguido do seu diretório público e faça commit do novo arquivo, para que o script do worker corresponda ao pacote 3.x em vez da antiga cópia 2.x.
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.
Star on GitHub12k