Testando Detalhes de Implementação.
Um teste faz asserções sobre como o código funciona internamente — campos privados, chamadas a métodos internos, estrutura do DOM ou classes CSS — em vez do comportamento observável do qual um consumidor real depende.
##Signs and Symptoms
Você reconhece este sintoma quando um teste vai além do contrato público e fixa a maquinaria por trás dele. Sinais comuns:
- Asserções sobre estado/campos internos ou privados, ou invocação direta de métodos privados (muitas vezes via casts, reflexão ou
// @ts-expect-error). - Espionar ou afirmar que um helper interno foi chamado (
expect(internalCalc).toHaveBeenCalled()) em vez de verificar o resultado. - Testes de UI que consultam por classe CSS, tag, hooks
data-*ou posição no DOM em vez de por role/label/texto — por exemplocontainer.querySelector('.btn-primary > span:nth-child(2)'),wrapper.state(),wrapper.find('SomeChildComponent').props(). - Testes de snapshot sobre árvores de renderização inteiras / objetos internos serializados, de modo que qualquer ajuste de marcação falha.
- Testes que quebram a cada refatoração mesmo que a funcionalidade continue funcionando (falsos negativos) e, inversamente, continuam passando após um bug de lógica porque apenas reverificam a ligação interna (falsos positivos).
// SMELL: acopla o teste aos internos do componente e à estrutura do DOM
test('counter increments', () => {
const wrapper = mount(<Counter />);
wrapper.instance().handleClick(); // chama um método privado diretamente
expect(wrapper.state('count')).toBe(1); // faz asserção sobre estado interno
expect(wrapper.find('.count-display').text()).toBe('1'); // seletor CSS frágil
});
O mesmo padrão aparece no lado servidor: expect(service._cache.size).toBe(1) ou afirmar a sequência exata de chamadas internas que um método faz.
##Reasons for the Problem
Por que acontece
- O estado interno é fácil de alcançar — um campo público, um helper exportado ou
container.querySelectorestá logo ali, enquanto exercitar o comportamento real exige mais preparação. - Perseguir métricas de cobertura: testar cada método privado 1:1 dá a sensação de minuciosidade.
- O uso excessivo de mocks empurra as pessoas a afirmar "este colaborador foi chamado" em vez de "a coisa certa aconteceu."
- Ferramentas que incentivam isso: shallow rendering / APIs
instance()/state(), ou capturar nós pelo nome da classe.
Por que é prejudicial
- Manutenibilidade / fragilidade. Este é o Teste Frágil de Meszaros, causado por Software Superespecificado: o teste fixa um comportamento que o consumidor nunca exigiu, de modo que refatorações inofensivas (renomear um método, reestruturar a marcação, alterar um campo privado) quebram testes que estavam verdes sem motivo real. Os testes se tornam um imposto sobre a refatoração em vez de uma rede de segurança.
- Falsa confiança (o perigo central, segundo Kent C. Dodds). Testes de detalhes de implementação falham nas duas direções erradas: falsos negativos (o teste fica vermelho embora a funcionalidade ainda funcione) e falsos positivos (o teste permanece verde embora a funcionalidade esteja quebrada — você verificou a ligação interna, não o resultado). De qualquer forma, a suíte deixa de lhe dizer a verdade.
- Legibilidade. O teste documenta como o código é construído, não o que ele garante. Quem lê não consegue identificar qual comportamento realmente importa, e o teste deixa de servir também como exemplo de uso ou especificação.
- Acoplamento. Ele fixa as decisões de design atuais, desencorajando exatamente as refatorações que os testes deveriam tornar seguras.
##Treatment
Teste através do contrato público — a mesma superfície que um chamador ou usuário real toca — e faça asserções sobre a saída observável: valores de retorno, erros lançados, eventos emitidos, estado persistido ou UI renderizada/visível.
- Identifique o consumidor. Para um módulo, é sua API exportada; para um componente de UI, é o usuário (cliques, digitação) e o que ele consegue ver.
- Forneça as entradas como um consumidor faria, e não chamando métodos privados. Dispare um clique real em vez de invocar o handler; chame o método público em vez do helper.
- Faça asserções sobre resultados, não sobre internos. Substitua verificações de
state()/campo privado/toHaveBeenCalledpor verificações sobre o que sai. - Consulte a UI por acessibilidade, não por estrutura — role, label, texto — em vez de classes CSS, tags ou
nth-child. - Pare de testar métodos privados diretamente. Cubra-os através do método público que os utiliza; se uma unidade privada for complexa o suficiente para precisar de seus próprios testes, isso é um sinal de que ela deve ser extraída para seu próprio módulo com sua própria API pública.
- Reserve asserções de mock/spy para fronteiras verdadeiras (rede, tempo, gateway de pagamento) onde a própria chamada é o comportamento observável — não para colaboradores internos.
// ANTES: testa detalhes de implementação
const wrapper = mount(<Counter />);
wrapper.instance().handleClick();
expect(wrapper.state('count')).toBe(1);
expect(wrapper.find('.count-display').text()).toBe('1');
// DEPOIS: testa o comportamento observável via contrato público voltado ao usuário
render(<Counter />);
await userEvent.click(screen.getByRole('button', { name: /increment/i }));
expect(screen.getByText('1')).toBeInTheDocument();
Regra prática: se uma refatoração que preserva o comportamento quebra o teste, o teste estava verificando um detalhe de implementação.
##Detected by
- eslint-testing-library testing-library/no-node-access — no-node-access
- eslint-testing-library testing-library/no-container — no-container