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

Mocks no Node.js Test Runner

Atualizado em: 16 de agosto de 2026

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

Os Mocks no Node.js Test Runner permitem substituir funções, métodos, timers e módulos durante testes para observar chamadas, controlar resultados e simular falhas. A API nativa reduz a necessidade de instalar uma biblioteca apenas para criar spies e stubs em projetos simples.

Mocks são úteis quando o código depende de rede, relógio, banco, fila, filesystem ou serviços externos. Porém, testes que simulam tudo podem passar mesmo quando a integração real está quebrada. A melhor estratégia combina testes unitários com mocks pequenos e testes de integração com dependências reais ou controladas.

Neste guia, você aprenderá a usar mock.fn(), mock.method(), analisar chamadas, configurar implementações, restaurar mocks, simular timers, isolar módulos, testar erros e evitar testes frágeis.

O que é a API de mocks?

O módulo node:test fornece um contexto de mock associado ao teste. A documentação oficial de mocks no Node.js Test Runner descreve funções e estabilidade. O módulo Assert oficial fornece verificações para resultados e erros.

Para a base do runner, consulte Node Test Runner. Para timers, veja Timers no Node.js. O artigo de Async Hooks no Node.js ajuda a compreender operações assíncronas.

Mock de função

const test = require('node:test');
const assert = require('node:assert/strict');

test('registra chamada', t => {
  const callback = t.mock.fn();

  callback('hello', 42);

  assert.equal(callback.mock.callCount(), 1);
});

O mock registra chamadas e pode executar uma implementação definida.

Implementação do mock

const calculate = t.mock.fn((a, b) => a + b);

assert.equal(calculate(2, 3), 5);

A função mantém o comportamento configurado e registra argumentos, resultado, contexto e erros.

Inspecionando chamadas

const call = calculate.mock.calls[0];

assert.deepEqual(call.arguments, [2, 3]);
assert.equal(call.result, 5);

A estrutura exata das chamadas pode evoluir. Consulte a documentação da versão usada.

callCount()

assert.equal(calculate.mock.callCount(), 1);

Contagem é útil, mas evite testar detalhes irrelevantes. O resultado de negócio costuma ser mais importante que o número exato de chamadas internas.

Mock de método

const service = {
  async fetchUser(id) {
    return database.findUser(id);
  }
};

test('usa usuário simulado', async t => {
  t.mock.method(
    service,
    'fetchUser',
    async id => ({ id, name: 'Ana' })
  );

  const user = await service.fetchUser(1);
  assert.equal(user.name, 'Ana');
});

O método original é substituído durante o teste.

Restauração automática

Mocks criados pelo contexto do teste são normalmente restaurados quando o teste termina. Isso reduz vazamento entre casos.

Restauração manual

const mocked = t.mock.method(
  service,
  'fetchUser',
  async () => null
);

mocked.mock.restore();

Restaure cedo quando o restante do mesmo teste precisa do método real.

resetCalls()

callback.mock.resetCalls();

Isso limpa histórico sem necessariamente remover a implementação. Use para separar fases do mesmo teste, embora testes menores sejam preferíveis.

Alterando implementação

callback.mock.mockImplementation(() => 'second');

O nome exato do método depende da versão. Fixe uma versão mínima e consulte a API suportada.

Implementação por chamada

Algumas versões permitem definir implementação para uma chamada específica:

callback.mock.mockImplementationOnce(
  () => 'temporary'
);

Esse recurso ajuda a simular retry, mas pode tornar o teste difícil de ler. Prefira uma implementação baseada em estado explícito quando necessário.

Simulando erro

t.mock.method(
  client,
  'request',
  async () => {
    throw new Error('Service unavailable');
  }
);

await assert.rejects(
  () => loadData(),
  /Service unavailable/
);

Testando retry

let attempts = 0;

t.mock.method(client, 'request', async () => {
  attempts += 1;

  if (attempts < 3) {
    throw new Error('Temporary failure');
  }

  return { ok: true };
});

const result = await loadWithRetry();
assert.equal(result.ok, true);
assert.equal(attempts, 3);

Veja Retry com Backoff no Node.js.

Mock de dependência por injeção

A forma mais simples de testar é receber dependências:

function createUserService({ repository, clock }) {
  return {
    async create(input) {
      return repository.insert({
        ...input,
        createdAt: clock.now()
      });
    }
  };
}

No teste, passe objetos pequenos em vez de alterar módulos globais.

Spy versus stub

  • Spy: observa chamadas, podendo manter comportamento real.
  • Stub: substitui comportamento por resposta controlada.
  • Fake: implementação simplificada funcional, como repositório em memória.

Escolha o mínimo necessário.

Mock parcial

Ao substituir apenas um método, os outros permanecem reais. Isso pode ser útil, mas também criar teste híbrido difícil de entender.

Mock de relógio

Evite depender de new Date() diretamente. Injete clock:

const clock = {
  now: () => new Date('2026-01-01T00:00:00Z')
};

Para timers, use a API nativa de mock timers quando suportada.

Mock timers

test('expira sessão', t => {
  t.mock.timers.enable({
    apis: ['setTimeout', 'Date']
  });

  let expired = false;

  setTimeout(() => {
    expired = true;
  }, 1000);

  t.mock.timers.tick(1000);
  assert.equal(expired, true);
});

A assinatura e a lista de APIs dependem da versão do Node.js.

Avançando tempo

t.mock.timers.tick(5000);

O tempo virtual torna o teste rápido e determinístico.

runAll()

Versões compatíveis podem oferecer método para executar todos os timers pendentes. Use com cuidado: um timer recorrente pode criar loop.

Timers assíncronos

Callbacks podem agendar Promises e novos timers. Após avançar o relógio, talvez seja necessário aguardar a fila de microtasks:

await Promise.resolve();

Restaurando timers

O contexto do teste limpa timers simulados ao final. Ainda assim, evite misturar relógio real e virtual no mesmo caso.

Date

Quando Date é mockado, confirme o instante inicial e avance de forma explícita. Testes de timezone devem usar datas ISO com offset.

Mock de módulo

O suporte a mock de módulos evoluiu nas versões recentes. Ele pode permitir substituir exports ESM ou CommonJS antes da importação.

Como a API pode estar experimental, consulte a documentação e fixe o runtime no CI.

Importação após o mock

Módulos são armazenados em cache. Configure o mock antes de importar o código que captura a dependência.

ES Modules

Bindings ESM são estáticos e não devem ser alterados por monkey patch comum. Use injeção de dependência ou API de mock de módulos suportada.

Veja ES Modules no Node.js.

CommonJS

Alterar require.cache manualmente é frágil e pode deixar estado global. Prefira interfaces públicas do runner.

Mocks de filesystem

Para código simples, injete uma interface:

function createLoader(fs) {
  return {
    load: path => fs.readFile(path, 'utf8')
  };
}

Para integração, use diretório temporário real.

Consulte File System no Node.js.

Mocks de fetch

Injete a função:

function createApiClient(fetchFn = fetch) {
  return {
    async getUser(id) {
      const response = await fetchFn(
        `https://api.example.com/users/${id}`
      );
      return response.json();
    }
  };
}

O teste passa uma função que retorna Response controlada.

Response real

const fetchMock = async () => new Response(
  JSON.stringify({ id: 1, name: 'Ana' }),
  {
    status: 200,
    headers: {
      'content-type': 'application/json'
    }
  }
);

Veja Fetch Nativo no Node.js.

Mocks de banco

Mocks validam lógica, mas não detectam SQL inválido, constraints ou transações. Mantenha testes de integração com banco real isolado.

Mocks de fila

Uma fake queue em memória pode registrar jobs. Testes de integração devem verificar serialização, retries e concorrência na tecnologia real.

Não teste implementação demais

Este teste é frágil:

assert.equal(repository.save.mock.callCount(), 1);
assert.equal(logger.info.mock.callCount(), 2);

Se o comportamento observável continua correto, uma refatoração interna não deveria quebrar o teste.

Teste contrato

Verifique dados enviados quando fazem parte do contrato:

assert.deepEqual(
  repository.save.mock.calls[0].arguments[0],
  {
    email: 'ana@example.com',
    active: true
  }
);

Ordem de chamadas

Teste ordem apenas quando ela afeta resultado, como iniciar transação antes de gravar.

Callbacks

const callback = t.mock.fn();
await execute(callback);

assert.equal(callback.mock.callCount(), 1);

Para APIs modernas, prefira Promises, mas mocks ajudam a testar bridges legadas.

Contexto this

As chamadas registradas podem incluir this. Isso ajuda a testar métodos que dependem do contexto, embora funções puras sejam mais simples.

Erros lançados pelo mock

O histórico pode registrar erro. Não exponha segredos em mensagens simuladas ou snapshots.

Testes concorrentes

Mocks que alteram objetos globais podem interferir em testes executados em paralelo. Use contexto local e evite monkey patch global.

Subtests

Cada subtest deve criar seus próprios mocks. Não compartilhe histórico mutável entre casos.

Cleanup

Mesmo com restauração automática, feche servidores, arquivos e conexões criados pela implementação fake. Mock não substitui cleanup.

Observabilidade do teste

Quando um teste falha, mensagens devem mostrar argumentos relevantes sem despejar tokens ou objetos gigantes.

Testando AbortSignal

const operation = t.mock.fn(async signal => {
  if (signal.aborted) {
    throw signal.reason;
  }
});

Consulte AbortController no Node.js.

Versão do runtime

A API de mock evolui rapidamente. Defina engines.node, fixe versão no CI e leia as notas da versão antes de atualizar.

Testes

Um conjunto equilibrado inclui:

  • função pura sem mock;
  • serviço com dependência injetada;
  • erro externo simulado;
  • timeout com relógio virtual;
  • integração real com filesystem;
  • integração real com banco;
  • teste de contrato de API;
  • teste end-to-end crítico.

Erros comuns

  • Mockar tudo: o teste não representa o sistema.
  • Alterar global: testes paralelos interferem.
  • Não restaurar: outros casos herdam comportamento.
  • Testar contagem irrelevante: refatorações quebram testes.
  • Usar timer real: suíte fica lenta e instável.
  • Mock de banco como única cobertura: SQL real não é validado.
  • Depender de API experimental sem fixar Node: CI quebra.

Boas práticas

  • Prefira injeção de dependência.
  • Use mocks pequenos.
  • Teste comportamento observável.
  • Restaure alterações.
  • Use relógio virtual.
  • Evite globals.
  • Mantenha integrações reais.
  • Fixe a versão do Node.js.
  • Faça cleanup.
  • Não registre dados sensíveis.

Conclusão

Os Mocks no Node.js Test Runner oferecem funções, métodos e timers simulados para criar testes rápidos e determinísticos sem depender de uma biblioteca externa em muitos casos.

O melhor uso é seletivo. Mocks devem controlar fronteiras instáveis, enquanto a lógica principal continua sendo testada por resultados. Com injeção de dependência, restauração automática, timers virtuais e testes de integração complementares, a suíte permanece confiável sem ficar presa à implementação interna.

Os 10 Melhores Cursos de Programação de 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