General Fixture.
Uma configuração compartilhada constrói uma única fixture gigante, do tipo “tudo em um”, que cobre as necessidades de todos os testes, mas cada teste individual exercita apenas uma pequena fatia dela.
##Signs and Symptoms
Um único beforeEach/setUp (ou uma configuração compartilhada no nível do módulo) constrói muitos objetos, popula muitos registros e prepara vários mocks — e cada teste em particular usa apenas um ou dois deles. A heurística de detecção do catálogo de test smells é simplesmente: nem todos os campos criados na configuração são usados por todos os métodos de teste.
Como reconhecê-la:
- O bloco de configuração é longo e cresce toda vez que um novo teste é adicionado.
- Para entender um único teste, você precisa rolar a tela para cima e fazer engenharia reversa de quais partes da fixture realmente importam para ele.
- Muitos testes falham ou precisam ser editados quando você altera um objeto compartilhado com o qual a maioria deles nem se importa.
- A mesma fixture atende, a partir de um único lugar, cenários radicalmente diferentes (criação, validação, pagamento, permissões).
// Smell: uma fixture para tudo; cada teste usa só uma fração dela
let user, admin, product, cart, order, paymentGateway, shippingZone;
beforeEach(() => {
user = createUser({ name: 'Ada' });
admin = createAdmin();
product = createProduct({ price: 10 });
cart = createCart(user, [product]);
order = createOrder(cart);
paymentGateway = mockGateway();
shippingZone = createZone('EU');
});
test('cart subtotal sums its line items', () => {
// só precisa de cart + product, mas paga por admin, order, gateway, zone…
expect(cart.subtotal()).toBe(10);
});
##Reasons for the Problem
Por que acontece
- DRY aplicado de forma agressiva demais. A configuração é consolidada em um único hook compartilhado para “evitar duplicação”, então as necessidades de cada novo teste são acopladas à mesma fixture.
- Crescimento orgânico. A fixture vai acumulando objetos à medida que testes são adicionados, e ninguém poda o que os testes antigos não usam mais.
- Inércia da configuração implícita. Uma vez que existe um
beforeEachgrande, adicionar mais uma linha é mais fácil do que criar uma fixture focada para o novo caso.
Por que é prejudicial
- Legibilidade / Testes como Documentação. Um teste deveria se ler como uma relação clara de causa→efeito. Quando a maior parte da fixture é ruído irrelevante, o leitor não consegue dizer o que de fato determina o resultado. O antídoto de Meszaros, a Minimal Fixture (fixture mínima), existe justamente porque um teste que usa a menor fixture possível é sempre mais fácil de entender.
- Manutenibilidade. Uma mudança exigida por um teste (por exemplo, dar a
productum novo campo obrigatório) força edições na configuração compartilhada por todos os testes, gerando quebras em cascata e acoplando testes não relacionados entre si. - Confiabilidade. Um estado de fixture compartilhado e mutável permite que os testes influenciem uns aos outros e torna difícil localizar as falhas — uma fonte clássica de instabilidade dependente da ordem de execução.
- Velocidade. Cada teste paga o custo total de construir a fixture inteira. A General Fixture é listada entre as causas de Slow Tests (testes lentos).
- Falsa confiança. Os testes ficam superespecificados em relação a detalhes incidentais da fixture, de modo que passam ou falham por razões não relacionadas ao comportamento sob teste.
##Treatment
Busque uma Minimal Fixture (fixture mínima): cada teste configura apenas o que aquele teste precisa, e nada mais.
- Inventarie o uso. Para cada campo compartilhado, liste quais testes de fato o leem. Tudo o que é usado apenas por um subconjunto é candidato a sair da configuração compartilhada.
- Empurre a construção para Creation Methods / builders de dados de teste (Delegated Setup). Mantenha os testes concisos sem forçar uma fixture gigante — cada teste chama um builder e sobrescreve apenas os atributos relevantes para ele.
- Prefira uma Fresh Fixture por teste em vez de uma compartilhada de vida longa, eliminando o acoplamento entre testes e o estado compartilhado mutável.
- Divida por cenário. Se grupos de testes realmente compartilham uma fixture pequena, separe-os em blocos
describefocados (ou classes de teste), cada um com sua própria configuração mínima. - Mantenha no
beforeEachapenas o que é verdadeiramente comum e pequeno; mova o restante para dentro do teste, de modo que causa e efeito fiquem ao lado da asserção.
// Antes: general fixture em um hook compartilhado (ver Sinais e Sintomas)
// Depois: configuração mínima e delegada — cada teste constrói só o que precisa
test('cart subtotal sums its line items', () => {
const cart = aCart().withItem(aProduct().price(10)).build();
expect(cart.subtotal()).toBe(10);
});
test('checkout charges the payment gateway', () => {
const gateway = mockGateway();
const cart = aCart().withItem(aProduct().price(10)).build();
checkout(cart, gateway);
expect(gateway.charge).toHaveBeenCalledWith(10);
});
Agora cada teste se lê de cima a baixo como uma história autocontida, os builders absorvem o código repetitivo e mudar um cenário não perturba mais os demais.