Ótimo ponto abordado e explicação muito assertiva, mas fiquei com uma dúvida, quando você disse a respeito de tornar a função mais testavel logo me veio a mente o TDD e pensando por esse lado não dificultaria mais o processo de escrever os testes?
Acho que é uma ótima medida pra uma refatoração no código posteriormente mas não sei se a melhor das óticas para encarar o desenvolvimento desses tipos de funções.
ps: sou só um estudante de programação, me perdoa se falei groselha
Pelo contrário, injeção de dependência anda de mãos dadas com TDD, principalmente se a gente estiver desenvolvendo "de cima para baixo".
Por exemplo, vamos voltar pro exemplo do payWithRetry
do post:
const payWithRetry = async (payRetryTimes, creditCard) => {
let times = 1;
while (true) {
try {
await pay(creditCard);
return;
} catch(error) {
if(times < payRetryTimes) {
times++;
continue;
}
throw error;
}
}
};
Imagine que neste momento a gente ainda não tem a implementação do pay
(pois estamos desenvolvendo "de cima pra baixo") e, antes de ter escrito esta implementação, a gente tivesse começado com o seguinte teste:
describe("When pay fails more times than `payRetryTimes`", () => {
it("Throws error", () => {
const creditCard = {
//...
};
expect(payWithRetry(3, creditCard)).rejects.toThrow();
});
});
Como é que a gente faz pra de fato testar este caso específico do payRetryTimes
?
O primeiro obstáculo é que pay
ainda não está implementado e, segundo, mesmo que estivesse, como ele chama um serviço externo, não é fácil fazer forçar a falha dele pra que nós possamos testar este caso.
É aí que entra a injeção de dependência.
Primeiro a gente ajusta a implementação de payWithRetry
pra receber pay
como parâmetro:
export const makePayWithRetry =
({ pay }) =>
async (payRetryTimes, creditCard) => {
let times = 1;
while (true) {
try {
await pay(creditCard);
return;
} catch (error) {
if (times < payRetryTimes) {
times++;
continue;
}
throw error;
}
}
};
Depois, ajustamos nosso teste pra passarmos uma versão mockada de pay
que vai se comportar exatamente como precisamos para o nosso teste:
describe("When pay fails more times than `payRetryTimes`", () => {
it("Throws error", () => {
const payMock = jest.fn().mockImplementation(() => {
throw new Error("");
});
const creditCard = {
//...
};
const payWithRetry = makePayWithRetry({ pay: payMock });
expect(payWithRetry(3, creditCard)).rejects.toThrow();
});
});
Veja que, desta maneira, nós podemos testar a lógica do payWithRetry
mesmo sem termos a implementação do pay
.