O Undici MockAgent no Node.js intercepta requisições feitas pelas APIs do Undici e devolve respostas programadas, sem acessar a rede. Ele é útil para testar clientes HTTP, integrações externas, retries, timeouts e parsing, mantendo controle sobre método, caminho, headers, body e quantidade de chamadas.
MockAgent atua como um Dispatcher. Para interceptar fetch ou request, ele pode ser definido como dispatcher global ou passado por requisição. Requisições sem handler podem ser bloqueadas, impedindo acesso acidental à internet durante testes.
Neste guia, você aprenderá a configurar MockAgent, criar interceptors, simular respostas e erros, validar chamadas pendentes, controlar conexões reais e integrar ao Node Test Runner.
O que é MockAgent?
A documentação oficial do MockAgent do Undici define a classe como um Dispatcher estável que intercepta requests e devolve respostas mockadas.
Ele não intercepta sozinho. É necessário:
- criar o MockAgent;
- obter um MockPool ou MockClient;
- registrar interceptors;
- definir o dispatcher;
- fechar após o teste.
Instalação
npm install --save-dev undiciEmbora Node.js use Undici internamente para fetch, instalar o pacote oferece acesso explícito às APIs de mock e fixa a versão usada no teste.
Configuração básica
import {
MockAgent,
setGlobalDispatcher
} from 'undici';
export const mockAgent = new MockAgent();
mockAgent.disableNetConnect();
setGlobalDispatcher(mockAgent);disableNetConnect() faz qualquer request não interceptado falhar.
Primeiro interceptor
const pool = mockAgent.get('https://api.example.com');
pool
.intercept({
method: 'GET',
path: '/status'
})
.reply(200, {
status: 'available'
}, {
headers: {
'content-type': 'application/json'
}
});Depois:
const response = await fetch(
'https://api.example.com/status'
);
const body = await response.json();Integração com Node Test Runner
import {
before,
after,
afterEach
} from 'node:test';
import {
Agent,
MockAgent,
setGlobalDispatcher
} from 'undici';
const mockAgent = new MockAgent();
before(() => {
mockAgent.disableNetConnect();
setGlobalDispatcher(mockAgent);
});
afterEach(() => {
mockAgent.assertNoPendingInterceptors();
mockAgent.clearCallHistory();
});
after(async () => {
await mockAgent.close();
setGlobalDispatcher(new Agent());
});Consulte Node Test Runner.
Dispatcher global
setGlobalDispatcher() afeta APIs que usam o dispatcher global, como fetch e request. Ele não substitui instâncias de Pool ou Client criadas separadamente.
Quando seu código aceita um dispatcher, prefira injeção explícita:
export async function getStatus({ dispatcher }) {
const response = await fetch(
'https://api.example.com/status',
{ dispatcher }
);
return response.json();
}Mock por requisição
const response = await fetch(url, {
dispatcher: mockAgent
});Esse modelo evita alterar estado global e facilita paralelismo.
Matching de origem
String exata:
mockAgent.get('https://api.example.com');Regex:
mockAgent.get(/^https:\/\/api\.example\.com$/);Função:
mockAgent.get(origin =>
origin === 'https://api.example.com'
);Prefira string exata quando possível.
Matching de query
pool.intercept({
method: 'GET',
path: '/products?limit=10&status=active'
}).reply(200, { data: [] });A ordem e serialização podem afetar o match. Para queries dinâmicas, use matcher compatível ou normalize a URL no cliente.
Matching de body
pool.intercept({
method: 'POST',
path: '/charges',
body: JSON.stringify({
amount: 1000,
currency: 'BRL'
})
}).reply(201, {
id: 'charge-1',
status: 'approved'
});O body precisa corresponder à serialização esperada.
Matching de headers
pool.intercept({
method: 'GET',
path: '/account',
headers: {
authorization: 'Bearer test-token'
}
}).reply(200, {
id: 'account-1'
});Nunca use tokens reais em fixtures.
Resposta com função
pool.intercept({
method: 'GET',
path: '/time'
}).reply(200, () => ({
generatedAt: '2026-09-13T00:00:00Z'
}));Use valores fixos para manter o teste determinístico.
Resposta 500
pool.intercept({
method: 'GET',
path: '/report'
}).reply(500, {
title: 'Internal Server Error',
status: 500
}, {
headers: {
'content-type': 'application/problem+json'
}
});Veja Problem Details no Node.js.
Erro de rede
MockAgent permite responder com erro:
pool.intercept({
method: 'GET',
path: '/report'
}).replyWithError(
new Error('Connection reset')
);Teste separadamente erros de rede e status HTTP.
Resposta repetida
pool.intercept({
method: 'GET',
path: '/health'
}).reply(200, { status: 'ok' })
.times(3);O interceptor pode ser consumido uma quantidade definida de vezes.
Persistência
pool.intercept({
method: 'GET',
path: '/config'
}).reply(200, { feature: true })
.persist();Use persist somente para respostas comuns. Ele pode esconder chamadas em excesso.
Retries
Registre interceptors na ordem:
pool.intercept({
method: 'GET',
path: '/data'
}).reply(503, {});
pool.intercept({
method: 'GET',
path: '/data'
}).reply(503, {});
pool.intercept({
method: 'GET',
path: '/data'
}).reply(200, { value: 42 });Assim, você verifica retry sem contador global. Consulte Retry com Backoff no Node.js.
Requests pendentes
mockAgent.assertNoPendingInterceptors();O método falha quando um interceptor esperado não foi consumido. Isso detecta código que deixou de chamar uma dependência importante.
pendingInterceptors
const pending = mockAgent.pendingInterceptors();
console.log(pending);Use para diagnóstico quando a assertion falhar.
Call history
const mockAgent = new MockAgent({
enableCallHistory: true
});
// após requests
const history = mockAgent.getCallHistory();
const first = history?.firstCall();A call history registra método, origem, path, headers e body conforme disponíveis. Limpe entre testes.
Assertions de chamadas
Use call history apenas quando o contrato de request é o comportamento testado. Evite assertions excessivas sobre ordem e headers internos.
Bloqueando rede
mockAgent.disableNetConnect();Esse deve ser o default em testes unitários e de integração isolados.
Permitindo localhost
mockAgent.enableNetConnect(host => {
return host.startsWith('127.0.0.1') ||
host.startsWith('localhost');
});Útil quando a aplicação local é real, mas dependências externas são mockadas.
Permitindo host específico
mockAgent.enableNetConnect('localhost:54321');Evite allowlists amplas como regex que aceita qualquer domínio.
activate e deactivate
mockAgent.deactivate();
mockAgent.activate();Normalmente não é necessário. Alterar estado no meio da suíte pode criar concorrência.
Fechando recursos
await mockAgent.close();O método fecha pools e limpa histórico. Esquecer pode manter handles ativos.
MockClient versus MockPool
Com uma conexão, get() pode retornar MockClient; com várias, MockPool. Na maioria dos testes, use a interface retornada sem depender da classe concreta.
Clientes que criam Pool
O dispatcher global não intercepta um Pool criado diretamente. Injete o mock:
const pool = mockAgent.get(origin);
const client = new PaymentsClient({ dispatcher: pool });Dependency injection melhora testabilidade sem monkeypatch.
fetch nativo
Versões atuais do Node.js usam Undici para fetch, mas diferenças entre a versão interna e o pacote instalado podem existir. Teste na versão real do runtime.
Consulte Fetch Nativo no Node.js.
MockAgent versus MSW
Mock Service Worker no Node.js oferece handlers compartilháveis com navegador, GraphQL, SSE e WebSocket. MockAgent é mais direto para clientes Undici e controle de Dispatcher.
Testes paralelos
Um dispatcher global compartilhado pode causar colisões. Prefira um MockAgent por arquivo ou dispatcher por request. Não compartilhe interceptors mutáveis entre testes concorrentes.
TypeScript
Crie helpers tipados:
function mockJson<T>(
pool,
path: string,
body: T,
statusCode = 200
) {
return pool.intercept({
method: 'GET',
path
}).reply(statusCode, body, {
headers: {
'content-type': 'application/json'
}
});
}CI
Com rede desativada, a suíte não depende da internet:
- run: npm ci
- run: npm run typecheck
- run: npm testConsulte CI para Node.js com GitHub Actions.
Segurança
- Use domínios example.com.
- Não use tokens reais.
- Desative rede.
- Limpe call history.
- Feche o agent.
- Fixe a versão do Undici.
- Não registre bodies sensíveis.
Erros comuns
- Sem dispatcher: interceptor não é usado.
- Pool criado separadamente: global não intercepta.
- Rede habilitada: teste acessa produção.
- Interceptor persistente: chamadas extras passam.
- Não verificar pendentes: request esperado não ocorre.
- Estado global paralelo: testes colidem.
- Não fechar: processo mantém handles.
- Confundir erro de rede e 500: lógica fica incompleta.
Configuração recomendada
import {
Agent,
MockAgent,
setGlobalDispatcher
} from 'undici';
export function createHttpMocks() {
const mockAgent = new MockAgent({
enableCallHistory: true
});
mockAgent.disableNetConnect();
setGlobalDispatcher(mockAgent);
return {
mockAgent,
pool(origin: string) {
return mockAgent.get(origin);
},
async close() {
mockAgent.assertNoPendingInterceptors();
await mockAgent.close();
setGlobalDispatcher(new Agent());
}
};
}Conclusão
O Undici MockAgent no Node.js oferece controle direto sobre requests feitos por fetch e outras APIs do Undici. Interceptors permitem programar respostas, falhas, retries e quantidade de chamadas sem abrir uma conexão real.
Desative a rede, verifique interceptors pendentes e feche o dispatcher. Com injeção explícita em clientes próprios, MockAgent produz testes rápidos e determinísticos sem depender de monkeypatch global.



