É comum ver o uso de funções beforeEach
e afterEach
para acabar com a repetição de código nos testes, mas isso pode custar caro.
describe("ShoppingCart", () => {
let cart, el;
beforeEach(() => {
const container = document.createElement('div');
cart = new ShoppingCart();
renderCartDOM(container, cart);
el = container.querySelector('#discountPercentage');
});
it("does not give a discount initially", () => {
cart.addItem("Apple");
expect(el.textContent).toBe('0');
});
it("gives a 5% discount when a book is added", () => {
cart.addItem("Book");
expect(el.textContent).toBe('5');
});
});
Nesse caso, os testes parecem menores porque todo o código que estaria duplicado está dentro do beforeEach
, o que parece ser o cenário ideal para tornar os testes legíveis e fáceis de manter.
No início isso não parece tão ruim, mas essa estrutura começa a ficar complicada a medida que a quantidade de testes aumenta. Observe como o arquivo fica quando outros testes começam a ser adicionados.
describe("ShoppingCart", () => {
let cart, el;
beforeEach(() => {
const container = document.createElement('div');
cart = new ShoppingCart();
renderCartDOM(container, cart);
el = container.querySelector('#discountPercentage');
});
describe("without books", () => {
it("does not give a discount", () => {
cart.addItem("Apple");
expect(el.textContent).toBe('0');
});
it("does not give a discount when a book is removed", () => {
cart.addItem("Apple");
cart.addItem("Book");
cart.removeItem("Book");
expect(el.textContent).toBe('0');
});
});
describe("with books", () => {
it("gives a 5% discount", () => {
cart.addItem("Book");
expect(el.textContent).toBe('5');
});
it("keeps discount when at least one book is added", () => {
cart.addItem("Book");
cart.addItem("Book");
cart.removeItem("Book");
expect(el.textContent).toBe('5');
});
});
});
Agora faça o seguinte, observe apenas o último teste e responda a pergunta:
- Onde o
ShoppingCart
foi declarado e instanciado? - Quantos
beforeEach
afetam esse teste?
Você precisou fazer scroll para o topo do arquivo não é? Arquivos grandes nos fazem ficar dando scroll pra cima e pra baixo para entender o contexto dos testes. Isso nos faz perder muito tempo para ler e manter os testes, acaba se tornando uma tarefa bem cansativa.
Lixeira dos testes
Em alguns casos o beforeEach
tende a ser a lixeira dos arquivos de teste. Pessoas jogam todo tipo de coisa lá: coisas que só são usadas em alguns testes, coisas que afetam todos os testes, e coisas que ninguém mais usa.
Factory functions
Factory functions são funções que nos ajudam a construir objetos ou estados e reusar a mesma lógica em diferentes lugares. Como ficariam os mesmos testes usando factory functions?
describe("ShoppingCart", () => {
function setup() {
const container = document.createElement('div');
const cart = new ShoppingCart();
renderCartDOM(container, cart);
const el = container.querySelector('#discountPercentage');
return { cart, el };
}
describe("without books", () => {
it("does not give a discount", () => {
const { cart, el } = setup();
cart.addItem("Apple");
expect(el.textContent).toBe('0');
});
it("does not give a discount when a book is removed", () => {
const { cart, el } = setup();
cart.addItem("Apple");
cart.addItem("Book");
cart.removeItem("Book");
expect(el.textContent).toBe('0');
});
});
describe("with books", () => {
it("gives a 5% discount", () => {
const { cart, el } = setup();
cart.addItem("Book");
expect(el.textContent).toBe('5');
});
it("keeps discount when at least one book is added", () => {
const { cart, el } = setup();
cart.addItem("Book");
cart.addItem("Book");
cart.removeItem("Book");
expect(el.textContent).toBe('5');
});
});
});
O tamanho do código é praticamente o mesmo, mas o código é mais legível. Eliminamos o beforeEach
mas o código não perdeu nada em manutenibilidade. A quantidade de repetição que nós eliminamos não mudou muito, mas a legibilidade melhorou bastante. Não precisamos de tantos scrolls para entender onde e como um objeto foi criado, e fica bem claro quando ele foi criado e os parâmetros que foram passados na criação.
O describe
é legal para criar um contexto, mas cria um aninhamento no código que pode dificultar a leitura, nesse caso podemos remove-lo do código.
function setup() {
const container = document.createElement('div');
const cart = new ShoppingCart();
renderCartDOM(container, cart);
const el = container.querySelector('#discountPercentage');
return { cart, el };
}
test("does not give a discount", () => {
const { cart, el } = setup();
cart.addItem("Apple");
expect(el.textContent).toBe('0');
});
test("does not give a discount when a book is removed", () => {
const { cart, el } = setup();
cart.addItem("Apple");
cart.addItem("Book");
cart.removeItem("Book");
expect(el.textContent).toBe('0');
});
test("gives a 5% discount", () => {
const { cart, el } = setup();
cart.addItem("Book");
expect(el.textContent).toBe('5');
});
test("keeps discount when at least one book is added", () => {
const { cart, el } = setup();
cart.addItem("Book");
cart.addItem("Book");
cart.removeItem("Book");
expect(el.textContent).toBe('5');
});