Programação Por Equipe DevBras

Capa do artigo: Mock que mente: teste verde, produção vermelha e o teste de contrato que evita

Mock que mente: teste verde, produção vermelha e o teste de contrato que evita

Use dublê para isolar a sua regra e um teste de contrato com resposta real gravada para descobrir quando o fornecedor muda antes do cliente descobrir.

Segunda de manhã, a página de checkout mostra "prazo de entrega: undefined dias". Todos os 312 testes passaram no deploy de sexta. O fornecedor de frete renomeou o campo prazo_dias para prazo_entrega_dias na API, e o mock da sua suíte continuou devolvendo o nome antigo, porque foi você quem escreveu o mock. A resposta curta: mock verifica a conversa do seu código com o que você imagina do mundo; teste de contrato verifica se o mundo ainda fala aquilo. Mantenha os dublês para isolar a sua regra, mas guarde uma resposta real gravada, valide-a com o mesmo código de produção e, de tempos em tempos, compare com o ambiente de testes do fornecedor.

O objetivo não é abandonar mocks. É saber exatamente qual pergunta cada teste responde e ter pelo menos um teste que falha quando a fronteira muda.

Por que o mock fica velho sem ninguém perceber

Um mock é uma cópia do comportamento de outro sistema, escrita por quem consome esse sistema. No dia em que foi escrito, ele era fiel. Depois, o fornecedor evolui a API, publica uma nota de versão que ninguém leu, e a cópia continua igual. Nada no seu repositório mudou, então nenhum teste tem motivo para falhar.

O problema piora quando o mock está espalhado. Cada teste monta a sua versão da resposta, com os campos que lembrava naquele dia. Em pouco tempo existem oito respostas diferentes do mesmo fornecedor, e nenhuma é a real. Os testes passam porque estão conferindo o código contra a imaginação coletiva da equipe.

Existe uma orientação antiga, popularizada pelo livro Growing Object-Oriented Software, Guided by Tests: não faça mock de tipos que você não possui. A ideia é criar uma interface sua, pequena, na fronteira, e fazer mock dela. O cliente HTTP do fornecedor fica atrás de um adaptador, e o adaptador é testado contra respostas reais.

Como dividir o teste em duas perguntas

  1. Crie um adaptador para o fornecedor. Ele chama a API, lê a resposta e devolve um objeto no formato do seu domínio. É o único lugar que conhece os nomes de campo do fornecedor.
  2. Faça o adaptador falhar alto quando um campo essencial falta ou vem com tipo errado. Silêncio vira undefined na tela.
  3. Teste a sua regra com um dublê do adaptador, não da API HTTP. Esses testes são rápidos e respondem "minha regra está certa?".
  4. Grave uma resposta real do ambiente de testes do fornecedor, sem dados pessoais, e versione no repositório.
  5. Teste o adaptador contra a resposta gravada. Esse teste responde "meu código entende o que o fornecedor manda?".
  6. Agende uma verificação contra o ambiente de testes real, uma vez por dia, que roda o mesmo adaptador e alerta se falhar.

O adaptador que falha alto

No exemplo, a loja consulta a cotação de frete de uma transportadora fictícia, a TransLog.

// src/translog.js
function lerCotacao(resposta) {
  const prazo = resposta.prazo_entrega_dias;
  const valor = resposta.valor_centavos;
  if (!Number.isInteger(prazo) || !Number.isInteger(valor)) {
    throw new Error('Resposta da TransLog fora do contrato: ' + JSON.stringify(resposta));
  }
  return { prazoDias: prazo, valorCentavos: valor };
}

async function cotar(cep, pesoGramas, opcoes = {}) {
  const url = (opcoes.baseUrl || process.env.TRANSLOG_URL) + '/v2/cotacao';
  const resposta = await fetch(url, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + process.env.TRANSLOG_TOKEN },
    body: JSON.stringify({ cep_destino: cep, peso_gramas: pesoGramas }),
    signal: AbortSignal.timeout(5000),
  });
  if (!resposta.ok) throw new Error('TransLog respondeu ' + resposta.status);
  return lerCotacao(await resposta.json());
}

module.exports = { lerCotacao, cotar };

Repare em duas coisas. O resto do sistema só conhece prazoDias e valorCentavos, nomes seus. E a leitura confere o tipo, então uma renomeação vira um erro com a resposta inteira no log, não um "undefined dias" na tela do cliente. O limite de tempo da chamada segue a lógica de timeout antes do infinito.

O teste de regra com dublê

// test/checkout.test.js
const test = require('node:test');
const assert = require('node:assert/strict');
const { montarResumo } = require('../src/checkout');

test('resumo soma o frete ao total e mostra o prazo', async () => {
  const freteFalso = { cotar: async () => ({ prazoDias: 4, valorCentavos: 1890 }) };
  const resumo = await montarResumo({ subtotalCentavos: 10000, cep: '01001-000' }, freteFalso);
  assert.equal(resumo.totalCentavos, 11890);
  assert.equal(resumo.textoPrazo, 'Chega em até 4 dias úteis');
});

Esse dublê devolve o formato do seu domínio, não o JSON da TransLog. Se a TransLog mudar, ele não precisa mudar, e está certo assim: ele testa a sua regra.

O teste de contrato com resposta gravada

// test/contratos/translog.test.js
const test = require('node:test');
const assert = require('node:assert/strict');
const { lerCotacao } = require('../../src/translog');
const exemplo = require('./translog-cotacao-2026-10.json');

test('adaptador entende a resposta real gravada da TransLog', () => {
  const cotacao = lerCotacao(exemplo);
  assert.ok(cotacao.prazoDias > 0);
  assert.ok(cotacao.valorCentavos > 0);
});

O arquivo translog-cotacao-2026-10.json é a resposta que o ambiente de testes devolveu, copiada sem edição manual. O nome com a data diz quando foi capturada. Quando o fornecedor anunciar mudança, você grava a nova resposta, o teste quebra de propósito e você ajusta o adaptador antes do deploy.

A verificação diária contra o ambiente real

A resposta gravada envelhece igual ao mock, só que mais devagar. Para fechar o ciclo, um script roda o adaptador contra o ambiente de testes do fornecedor:

// scripts/verificar-translog.js
const { cotar } = require('../src/translog');

cotar('01001-000', 500, { baseUrl: process.env.TRANSLOG_SANDBOX_URL })
  .then((c) => console.log('Contrato TransLog ok:', c))
  .catch((erro) => {
    console.error('Contrato TransLog quebrou:', erro.message);
    process.exit(1);
  });

Agende esse script no CI uma vez por dia, separado do build principal, e faça a falha abrir um alerta para quem cuida da integração. Ele não deve bloquear o deploy de quem mexeu em outra parte do sistema; a função dele é avisar cedo. Para integrações entre serviços da própria empresa, ferramentas de contrato orientado ao consumidor, como o Pact, formalizam esse acordo dos dois lados.

Erros comuns

  • Mockar o cliente HTTP em cada teste. Cada teste carrega uma versão da resposta, e nenhuma delas acompanha o fornecedor.
  • Escrever a resposta de exemplo à mão. Ela reflete o que você leu na documentação, não o que a API manda; nomes, tipos e campos nulos divergem.
  • Adaptador que aceita qualquer coisa. Campo ausente vira undefined e só aparece na tela do cliente, longe do ponto onde o problema nasceu.
  • Gravar resposta com dado pessoal ou token. O arquivo vai para o repositório e o vazamento vai junto; limpe antes de versionar.
  • Colocar a chamada ao ambiente real no build de todo commit. Quando o fornecedor cai, o seu deploy para, sem relação com o que mudou.
  • Confiar só no ambiente de homologação. Ele ajuda, mas se for diferente de produção também mente; o post ambiente de teste que não parece produção mostra onde.

Checklist

  • Cada fornecedor externo fica atrás de um adaptador seu.
  • O adaptador valida campos essenciais e falha com a resposta no log.
  • Testes de regra usam dublê do adaptador, no formato do seu domínio.
  • Existe uma resposta real gravada, datada e sem dados pessoais, para cada operação usada.
  • O adaptador tem teste contra essa resposta gravada.
  • Uma verificação agendada roda contra o ambiente de testes do fornecedor e alerta quando falha.
  • Alguém da equipe recebe as notas de versão da API do fornecedor.

Pense na última vez que a produção quebrou por causa de um fornecedor: algum teste seu poderia ter falhado antes, ou todos eles conversavam só com o mock que a equipe escreveu?

Avalie este artigo

Referência: https://martinfowler.com/bliki/ContractTest.html