Excessive Mocking.
Um teste configura tantos objetos mock e interações stubadas que a configuração dos mocks ofusca a verificação propriamente dita, então o teste acaba exercitando os mocks em vez do comportamento real.
##Signs and Symptoms
Um teste é majoritariamente arrange: longos blocos de criação de mocks, linhas de when(...).thenReturn(...) / mockReturnValue(...) e verify(...), com pouco código real sob teste. Sinais reveladores:
- Mais encanamento de mocks do que asserções. A configuração tem muitas linhas; o "act" é uma única chamada; o "assert" verifica que os mocks foram chamados em vez de que um resultado está correto.
- Mockar coisas que são suas. Objetos de domínio, objetos de valor ou colaboradores de lógica pura são mockados em vez de apenas as fronteiras externas (rede, banco, relógio, sistema de arquivos, terceiros).
- Cadeias de mocks / "train wrecks". Um mock retorna outro mock que retorna outro mock, espelhando o grafo de chamadas de produção.
- Verificação só de interação. O teste faz asserções
toHaveBeenCalledWith(...)em cada colaborador e nunca verifica o valor retornado ou o estado resultante. - Frágil em refatorações. Reordenar chamadas internas ou extrair um método quebra muitos testes mesmo que o comportamento permaneça inalterado.
- Cinco ou mais mocks só para instanciar o SUT — normalmente um sinal de que o próprio SUT tem dependências demais.
test('places order', () => {
const inventory = { check: jest.fn().mockReturnValue(true) };
const pricing = { quote: jest.fn().mockReturnValue(42) };
const tax = { calc: jest.fn().mockReturnValue(4.2) };
const wallet = { charge: jest.fn().mockReturnValue({ ok: true }) };
const ledger = { record: jest.fn() };
const emailer = { send: jest.fn() };
const audit = { log: jest.fn() };
const clock = { now: jest.fn().mockReturnValue(0) };
const svc = new OrderService(inventory, pricing, tax, wallet, ledger, emailer, audit, clock);
svc.place(cart);
// verifica que os mocks foram chamados — não que o pedido está correto
expect(inventory.check).toHaveBeenCalled();
expect(pricing.quote).toHaveBeenCalled();
expect(wallet.charge).toHaveBeenCalledWith(46.2);
expect(ledger.record).toHaveBeenCalled();
});
##Reasons for the Problem
Por que acontece
- O SUT tem colaboradores demais. Mocking excessivo costuma ser um sinal de design: uma classe com baixa coesão e muitas dependências força cada teste a montar todas elas. Como diz a literatura do catálogo, "se você precisa mockar cinco classes internas só para testar um método, o método tem dependências demais — isso é um problema de design, não de teste."
- Hábito / reflexo de "mockar tudo". Levar o teste baseado em interação ("escola de Londres") ao extremo, ou mockar por reflexo para evitar tocar em um banco ou na rede, leva a mockar lógica pura que poderia ser testada diretamente.
- Objetos reais difíceis de construir. Quando colaboradores reais são complicados de instanciar, um mock parece mais fácil do que consertar o construtor ou adicionar um fake.
Por que prejudica
- Falsa confiança. Mocks codificam as suas suposições sobre uma dependência. Se a implementação real divergir, o teste continua passando — por exemplo, um stub que afirma que
sum()retorna apenas inteiros positivos mantém o teste verde mesmo depois que o método real muda. Tais testes podem se tornar tautológicos: eles só verificam que os mocks que você escreveu se comportam como os mocks que você escreveu. Como alerta a própria documentação do Mockito, "se tudo está mockado, estamos realmente testando o código de produção?" - Fragilidade / alta manutenção. Verificar chamadas específicas trata detalhes de implementação como o contrato, então refatorações inofensivas (ordem das chamadas, helpers extraídos) quebram os testes. Isso é o Overspecified Software / Fragile Test de Meszaros.
- Baixa legibilidade. Centenas de linhas de configuração de dependências enterram a única coisa de que o teste trata, muito parecido com um Mystery Guest — um revisor não consegue dizer qual comportamento é de fato verificado.
- Bugs de integração que passam batido. A ligação real entre os componentes nunca é exercitada; os bugs vivem exatamente nas costuras que os mocks substituíram. Alta cobertura mascara baixa qualidade.
##Treatment
Trate o uso pesado de mocks como feedback e, então, reduza a necessidade de mockar:
- Conserte o design primeiro. Se você precisa mockar mais de 5 colaboradores para construir o SUT, divida responsabilidades ou reduza as dependências do construtor. Menos dependências reais significa menos mocks.
- Mocke apenas nas fronteiras da arquitetura. Mocke as coisas que não são suas (rede, banco, relógio, sistema de arquivos, APIs de terceiros); use instâncias reais dos seus próprios objetos de domínio e de valor.
- Extraia um núcleo puro (functional core / imperative shell). Mova a lógica de cálculo/decisão para fora da classe pesada em I/O para que ela possa ser testada com zero mocks, deixando uma casca fina que precisa de apenas um ou dois dublês de fronteira.
- Prefira verificação de estado em vez de verificação de interação. Verifique o valor retornado ou o estado resultante em vez de
verify(...)/toHaveBeenCalledWith(...)em cada colaborador. Reserve as verificações de interação para o único efeito colateral que realmente importa. - Use o dublê mais simples que funcione. Substitua mocks elaborados por stubs (apenas retornam valores) ou por um único fake em memória reutilizável em vez de re-stubar cada método a cada teste.
- Revele o excesso de mocks dinamicamente. Habilitar o strict stubbing do Mockito (o padrão
MockitoExtension/MockitoJUnitRunner) lançaUnnecessaryStubbingExceptionpara stubs que são configurados mas nunca usados — uma forma barata de encontrar mocks de que você não precisava.
// ANTES: 8 mocks, verificando chamadas
const pricing = { quote: jest.fn().mockReturnValue(42) };
const tax = { calc: jest.fn().mockReturnValue(4.2) };
// + mais 6 mocks ...
expect(wallet.charge).toHaveBeenCalledWith(46.2);
// DEPOIS: lógica pura testada diretamente — sem mocks
expect(totalFor(cart, rates)).toBe(46.2);
// apenas a fronteira real é falseada; verifique o estado, não as chamadas
const wallet = new InMemoryWallet({ balance: 100 });
const svc = new OrderService(new InMemoryInventory(cart), wallet);
const order = svc.place(cart);
expect(order.total).toBe(46.2);
expect(wallet.balance).toBe(53.8);