Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

Mock Service Worker no Node.js

Atualizado em: 13 de setembro de 2026

Estação de trabalho usada no desenvolvimento de aplicações Node.js

O Mock Service Worker no Node.js, conhecido como MSW, intercepta requisições HTTP feitas pelo processo e retorna respostas controladas durante testes e desenvolvimento. Em vez de substituir manualmente cada função de cliente, o teste descreve o comportamento da API externa no nível da rede.

Essa abordagem permite reutilizar handlers entre testes Node.js, navegador e demos. O código da aplicação continua usando fetch, Axios, GraphQL ou bibliotecas reais, enquanto MSW controla a resposta. Porém, mocks não substituem testes contra serviços reais e precisam ser resetados para evitar vazamento de estado entre casos.

Neste guia, você aprenderá a instalar MSW, criar handlers REST e GraphQL, integrar com Node Test Runner e Vitest, simular erros, delays, uploads e cenários dinâmicos, além de manter mocks alinhados ao contrato.

Como MSW funciona no Node.js?

A documentação oficial da integração Node.js do MSW explica que a biblioteca intercepta módulos responsáveis por requisições, incluindo mecanismos nativos usados por http, https e fetch.

Ela não sobe um servidor HTTP separado. O interceptador atua apenas no processo atual.

Instalação

npm install --save-dev msw

Mantenha MSW em devDependencies e fixe a versão pelo lockfile.

Primeiro handler

import { http, HttpResponse } from 'msw';

export const handlers = [
  http.get('https://payments.example.com/status', () => {
    return HttpResponse.json({
      status: 'available'
    });
  })
];

Configurando setupServer

import { setupServer } from 'msw/node';
import { handlers } from './handlers.js';

export const mockServer = setupServer(...handlers);

O nome setupServer pode causar confusão: ele cria um interceptador, não um servidor de rede acessível externamente.

Lifecycle no Node Test Runner

import { before, after, afterEach } from 'node:test';
import { mockServer } from './mocks/server.js';

before(() => {
  mockServer.listen({
    onUnhandledRequest: 'error'
  });
});

afterEach(() => {
  mockServer.resetHandlers();
});

after(() => {
  mockServer.close();
});

Veja Node Test Runner.

Por que resetar handlers?

Um teste pode adicionar comportamento temporário:

mockServer.use(
  http.get('https://payments.example.com/status', () => {
    return HttpResponse.json(
      { error: 'unavailable' },
      { status: 503 }
    );
  })
);

Sem resetHandlers(), o override pode afetar testes seguintes.

Falhar em requisições não tratadas

mockServer.listen({
  onUnhandledRequest: 'error'
});

Isso impede que um teste acesse a internet acidentalmente. Em suítes com requests locais reais, use uma função para permitir apenas hosts conhecidos.

Handler com parâmetros

http.get(
  'https://catalog.example.com/products/:productId',
  ({ params }) => {
    return HttpResponse.json({
      id: params.productId,
      name: 'Teclado',
      price: 19900
    });
  }
)

Query parameters

http.get('https://catalog.example.com/products', ({ request }) => {
  const url = new URL(request.url);
  const category = url.searchParams.get('category');

  return HttpResponse.json({
    data: category === 'hardware'
      ? [{ id: 'p1', name: 'Teclado' }]
      : []
  });
});

Não inclua query string na URL do handler; leia com URL.

Lendo JSON

http.post('https://payments.example.com/charges', async ({ request }) => {
  const body = await request.json();

  if (body.amount > 100000) {
    return HttpResponse.json(
      { code: 'limit_exceeded' },
      { status: 422 }
    );
  }

  return HttpResponse.json(
    { id: 'charge-1', status: 'approved' },
    { status: 201 }
  );
});

Headers e autenticação

http.get('https://api.example.com/account', ({ request }) => {
  const authorization = request.headers.get('authorization');

  if (authorization !== 'Bearer test-token') {
    return HttpResponse.json(
      { title: 'Unauthorized', status: 401 },
      {
        status: 401,
        headers: {
          'Content-Type': 'application/problem+json'
        }
      }
    );
  }

  return HttpResponse.json({ id: 'account-1' });
});

Consulte Problem Details no Node.js.

Simulando delay

import { delay, http, HttpResponse } from 'msw';

http.get('https://api.example.com/report', async () => {
  await delay(500);
  return HttpResponse.json({ ready: true });
});

Use delays pequenos e explícitos. Testes com tempo real ficam lentos; quando possível, teste timeouts com relógio controlado.

Network error

http.get('https://api.example.com/report', () => {
  return HttpResponse.error();
});

Esse cenário é diferente de uma resposta HTTP 500. Network error significa que não existe resposta válida.

Resposta 500

http.get('https://api.example.com/report', () => {
  return HttpResponse.json(
    { title: 'Internal Server Error', status: 500 },
    { status: 500 }
  );
});

Teste separadamente 500, timeout, conexão interrompida e JSON inválido.

Retries

Para simular falha seguida de sucesso:

let attempts = 0;

mockServer.use(
  http.get('https://api.example.com/data', () => {
    attempts += 1;

    if (attempts < 3) {
      return HttpResponse.json({}, { status: 503 });
    }

    return HttpResponse.json({ value: 42 });
  })
);

Resete o contador dentro do teste. Veja Retry com Backoff no Node.js.

Streaming

http.get('https://api.example.com/stream', () => {
  const stream = new ReadableStream({
    start(controller) {
      controller.enqueue(new TextEncoder().encode('chunk-1\n'));
      controller.enqueue(new TextEncoder().encode('chunk-2\n'));
      controller.close();
    }
  });

  return new HttpResponse(stream, {
    headers: { 'Content-Type': 'text/plain' }
  });
});

Consulte Web Streams API no Node.js.

Uploads

http.post('https://uploads.example.com/files', async ({ request }) => {
  const formData = await request.formData();
  const file = formData.get('file');

  if (!(file instanceof File)) {
    return HttpResponse.json({}, { status: 400 });
  }

  return HttpResponse.json({
    id: 'file-1',
    name: file.name,
    size: file.size
  });
});

GraphQL

import { graphql, HttpResponse } from 'msw';

export const handlers = [
  graphql.query('GetProduct', ({ variables }) => {
    return HttpResponse.json({
      data: {
        product: {
          id: variables.id,
          name: 'Teclado'
        }
      }
    });
  })
];

O nome da operação facilita handlers específicos.

Erros GraphQL

graphql.mutation('CreateOrder', () => {
  return HttpResponse.json({
    errors: [
      {
        message: 'Produto indisponível',
        extensions: { code: 'OUT_OF_STOCK' }
      }
    ]
  });
})

Handlers compartilhados

mocks/
├── handlers/
│   ├── catalog.ts
│   ├── payments.ts
│   └── users.ts
├── handlers.ts
├── node.ts
└── browser.ts

O mesmo array pode ser usado por setupServer no Node.js e setupWorker no navegador.

Cenários dinâmicos

Prefira overrides por teste:

mockServer.use(paymentDeclinedHandler);

Evite um objeto global de estado que vários testes alteram em paralelo.

Handlers por cenário

export const paymentScenarios = {
  approved: http.post(url, () =>
    HttpResponse.json({ status: 'approved' })),

  declined: http.post(url, () =>
    HttpResponse.json(
      { code: 'declined' },
      { status: 422 }
    ))
};

Assertions

MSW recomenda focar no resultado da aplicação em vez de transformar handlers em spies. Teste que o serviço retornou o estado esperado, não detalhes internos de cada request.

Quando o contrato da requisição é importante, valide o body dentro do handler e retorne erro claro.

Eventos de lifecycle

mockServer.events.on('request:start', ({ request }) => {
  console.log('MSW:', request.method, request.url);
});

Use para diagnóstico, não para logs permanentes no CI.

Bypass e passthrough

MSW pode deixar uma requisição real passar. Evite isso em testes unitários, pois a suíte passa a depender da rede. Use apenas em cenários de desenvolvimento controlados.

Contrato com OpenAPI

Handlers escritos manualmente podem ficar desatualizados. Gere tipos ou valide fixtures contra schemas.

Veja OpenAPI com Node.js.

TypeScript

type ChargeRequest = {
  amount: number;
  currency: 'BRL';
};

type ChargeResponse = {
  id: string;
  status: 'approved';
};

http.post<never, ChargeRequest, ChargeResponse>(
  url,
  async ({ request }) => {
    const body = await request.json();
    return HttpResponse.json({
      id: 'charge-1',
      status: 'approved'
    });
  }
)

Testes em paralelo

Overrides globais podem colidir entre testes concorrentes. Use isolamento, server.boundary() quando adequado ou evite executar o grupo em paralelo.

Não mockar a própria API

Para testar uma API Node.js, chame a aplicação em memória ou por HTTP local. MSW é mais útil para dependências externas chamadas pela API.

Testcontainers e MSW

Use MSW para serviços HTTP externos e Testcontainers no Node.js para banco, Redis e filas reais.

CI

- run: npm ci
- run: npm run typecheck
- run: npm test

Com onUnhandledRequest: error, o CI detecta qualquer acesso de rede não previsto.

Segurança

  • Não use tokens reais.
  • Não grave payloads sensíveis.
  • Não permita requests externos inesperados.
  • Use domínios example.com.
  • Mantenha fixtures fictícias.
  • Fixe a versão do MSW.
  • Feche o interceptador após os testes.

Erros comuns

  • Não resetar handlers: testes vazam estado.
  • Não fechar servidor: processo pode ficar ativo.
  • Unhandled como bypass: teste acessa internet.
  • Mock diferente do contrato: produção quebra.
  • Assertion em detalhes internos: teste fica frágil.
  • Override global paralelo: cenários colidem.
  • Delay real longo: suíte fica lenta.
  • Confundir network error com 500: tratamento errado.

Configuração recomendada

// test/mocks/server.ts
import { setupServer } from 'msw/node';
import { handlers } from './handlers.js';

export const mockServer = setupServer(...handlers);

// test/setup.ts
import { before, after, afterEach } from 'node:test';
import { mockServer } from './mocks/server.js';

before(() => {
  mockServer.listen({ onUnhandledRequest: 'error' });
});

afterEach(() => {
  mockServer.resetHandlers();
});

after(() => {
  mockServer.close();
});

Conclusão

O Mock Service Worker no Node.js intercepta HTTP no nível da rede e permite testar integrações sem alterar o cliente da aplicação. Handlers podem ser compartilhados com navegador, demos e diferentes test runners.

Falhe em requests não tratados, resete overrides e valide contratos. Com MSW para serviços HTTP e Testcontainers para infraestrutura, testes ganham realismo sem depender de ambientes externos instáveis.

10 melhores cursos de programação em 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita