Boas-vindas ao curso de Boas Práticas com Testes!
Sou Emerson Laranja, instrutor da Escola de Programação.
Audiodescrição: Emerson é um homem negro de barba e cabelos escuros. Usa óculos de grau quadrado, aparelho ortodôntico e uma camiseta verde. Ao fundo, uma parede lisa com iluminação degradê do verde ao azul.
Este conteúdo é destinado a quem deseja se aprofundar em testes, utilizando boas práticas observadas no Node.js.
Nessa jornada, você aprenderá o como e quando usar classes dublês e espiões. Além de como utilizar a Inteligência Artificial para gerar mais casos de testes, aumentando a cobertura de testes dos seus módulos.
Construiremos módulos a partir dos testes, o desenvolvimento guiado por testes, conhecido como TDD. Dentro do TDD faremos testes unitários, de integração, vamos separar o banco de dados de produção e o banco de dados para testes, além outras boas práticas.
Exploraremos tudo isso em uma API ToDo list, ou seja, lista de tarefas, onde, dando continuidade ao projeto desta formação, criaremos as funcionalidades de listar e atualizar tarefas.
Para que você tenha o melhor proveito, é importante que você tenha assistido os outros dois cursos dessa formação sobre SOLID e padrões de projeto. Afinal, nosso projeto é uma continuação.
Convidamos você a ir além de assistir aos vídeos: faça a leitura e as atividades deste curso, converse com outros estudantes na nossa comunidade do Discord e, claro, tire suas dúvidas no Fórum.
Vamos lá?
Começaremos relembrando nosso projeto. Temos uma API no back-end que funciona como uma ToDo
list e a funcionalidade de criar e deletar tarefas.
Agora, queremos finalizar esse CRUD com a opção de listar e atualizar as tarefas, porém garantindo uma maior qualidade do nosso código. Para isso, pensaremos nessas implementações a partir de testes.
Antes de começarmos a criar os testes de fato, vamos abrir no VS Code o projeto. Acessamos src > adapters > controllers > task > addTask.spec.ts
. Esse é um arquivo que deixamos pronto para começarmos a pensar em boas práticas na criação dos testes. Vamos analisá-lo.
//Código omitido
describe("AddTask Controller", () {
test("Deve chamar AddTask com valores corretos", async ()
const httpRequest = {
body: {
title: "any_title",
description: "any_description",
date: "30/06/2024",
},
};
await MongoManager.getInstance().connect(env.mongoUrl);
const taskMongoRepository = new TaskMongo Repository();
const dbAddTask = new DbAddTask(taskMongo Repository);
const addTaskController = new AddTaskController(
dbAddTask,
addTaskValidationCompositeFactory()
);
const httpResponse = await addTaskController.handle(httpRequest);
expect(httpResponse.statusCode).toBe(201);
expect(httpResponse.body.title).toBe("any_title");
expect(httpResponse.body.description).toBe("any_description");
expect(httpResponse.body.date).toBe("30/06/2024");
});
//Código omitido
Na linha 11 temos um test()
e o que ele faz. Nesse caso ele deve chamar o AddTask com valores corretos. Abaixo, na linha 12 criamos uma requisição HTTP passando alguns dados falsos como um any_title
, any_description
e uma data qualquer.
Próximo à linha 19, conectamos o teste ao banco de dados e fazemos a instância do nosso repositório. Fizemos as implementações anteriormente.
Após, chamamos o dbedtask
, que é quem sabe adicionar uma tarefa e passamos essas informações para o controller. Assim como chamamos quem faz as validações ao adicionar uma tarefa.
Na linha 27, chamamos o método handle()
e recebemos uma resposta que armazenamos em httpResponse
. Em seguida, após essa parte de instanciação, temos o teste.
Nele, verificamos se o que recebemos possui status 201, ou seja, que a tarefa foi criada, e também se possui os campos que passamos, any_title
, any_description
e a data 30/06/2024
.
Agora podemos executar esse teste. Para isso, abrimos o arquivo package.json
. Além do start
, deixamos mais dois scripts, o test
, onde visualizaremos o teste, e o test:verbose
, que é a visualização com mais detalhes.
Como só queremos executar o teste, abrimos o terminal e passamos o comando npm run test
, seguido de "Enter". Após ser executado, recebemos uma mensagem de erro relacionado ao timeout.
Isso significa que na linha 19 ele tentou conectar com o banco de dados, mas não teve sucesso. Isso porque o servidor realmente não foi inicializado.
Precisamos inicializá-lo, pois deixamos tudo no Docker. Então, em um novo terminal, passamos o comando docker compose up
, seguido de "Enter".
docker compose up
Após executar, voltamos ao terminal anterior e executamos novamente o npm run test
. Após aguardar alguns instantes, recebemos a mensagem que o teste passou.
Vamos entender qual é o problema desse código. Analisando-o novamente, o trecho da linha 12 até a 25 é para lidar com instanciações, o que atrapalha na legibilidade e manutenção do código.
A ideia do nosso teste é receber essas informações já prontas e apenas testar o que queremos, no caso verificar se os campos recebidos são iguais aos que passamos e se o status é 201.
O outro problema é relacionado ao banco de dados. Vamos acessá-lo para conseguirmos visualizar. Criamos um novo terminal e passamos o comando docker exec -it mongodb mongosh
, para acessar o container. Dentro, usaremos nossa tabela de testes, então passamos use tdd
e pressionamos "Enter".
use tdd
Após, para exibir as tabelas, usamos o comando show tables
.
show tables
Já temos a tabela de testes criadas, então para verificar as que possuem esse teste, executaremos o comando db.tasks.find()
, assim ele buscará todas as tarefas do banco.
db.tasks.find()
Aqui está o problema. Repare que nosso teste foi adicionado no banco de produção. Assim, no dia a dia teremos os dados que são fictícios misturados com os nossos dados reais, que também é um problema.
Para resolver, removeremos o trecho de código em que chamamos as classes reais que estão sendo usadas em produção. O que estamos nos preocupando é com o valor retornado. Então, criaremos algumas classes que vão simular esses valores retornados.
Apagamos da linha 9, onde temos o await,
até a linha 21, onde temos a const dbAddTask
. O controller precisa receber a simulação de quem sabe adicionar uma tarefa e também de quem sabe fazer uma validação.
Para isso acima do describe()
, próximo à linha 10, criaremos uma nova classe passando class AddTask
e a nomeamos de Stub
, relacionado a essa simulação.
Na mesma linha, passamos implements AddTask{}
. Feito isso, clicamos em AddTask
e depois em "AddTask" novamente para fazemos a implementação na pasta de "usecases".
Nas chaves, precisamos passar a descrição do método. Para isso, usaremos o atalho do VS Code de correção rápida clicando em AddTaskStub
e depois em "Implementar a interface 'AddTask'".
Antes de add()
, como se trata de uma adição, simularemos um método assíncrono, assim como é feito com o banco de dados. Depois, na linha abaixo, passamos return Promise.resolve({})
.
Quando a Promise
for resolvida, queremos ter como retorno a tarefa que possui um id:"any_id"
, seguido dos mesmos valores que passamos na requisição, title
, description
e date
. Ficando da seguinte forma:
//Código omitido
const makeAddTask = (): AddTask => {
class AddTaskStub implements AddTask {
async add(task: AddTaskModel): Promise<Task> {
return Promise.resolve({
id: "any_id",
title: "any_title",
description: "any_description",
date: "30/06/2024",
});
}
}
//Código omitido
Após, em addTaskController
, podemos substituir o dbAddTask
, próximo à linha 33, por new AddTaskStub()
. Faremos o mesmo para o validation
.
Abaixo da classe AddTaskSub
, criamos outra passando class ValidationStub implements Validation{}
. Clicamos em Validation
para fazer a implementação do método.
Esse método não precisa ser assíncrono, precisamos apenas pensar no valor que retornaremos. No caso de sucesso, onde não ocorreu
nenhum erro, o que é retornado do método Validate
é void
. Sendo assim, na linha abaixo podemos passar apenas return
.
//Código omitido
const makeValidation = (): Validation => {
class ValidationStub implements Validation {
validate(data: any): void | Error {
return;
}
}
//Código omitido
Em sequência, na constante addTaskController
, substituiremos a classe de produção pela simulação. Então, apagamos o addTaskValidationCompositeFactory()
e passamos new ValidationStub()
.
//Código omitido
const addTaskController = new AddTaskController(
new AddTaskStub(),
new ValidationStub()
);
//Código omitido
Com essas alterações já podemos testar novamente e verificar se está tudo correto. Abrimos o terminal onde estávamos executando os testes e passamos o comando clean
para limpá-lo. Após, passamos o comando npm test
e ele passa.
Observe que agora conseguimos diminuir as informações que estavam dentro do nosso teste.
Principalmente em relação à instanciação, a colocamos para fora, nas classes que demos o nome de Stub, que significa dublê em português.
É como se estivéssemos na produção de um filme de ação, onde nos cenários mais perigosos, como um saltar de um prédio pra outro, pendurado em uma teia, deixamos de lado o nosso ator principal e utilizamos os dublês.
Estamos fazendo o mesmo nos nossos testes. Não usaremos os cenários mais delicados onde estamos testando as classes de produção e sim criaremos dublês para atuarem.
Dessa forma, conseguimos diminuir a quantidade de linhas no código, deixando-o mais legível. Outro ponto de destaque é que em nenhum momento conectamos ao banco de dados. Isso significa que resolvemos o problema de misturar os dados de teste com os dados de produção.
Mas, podemos melhorar ainda mais esse teste. Onde temos quanto expect
, podemos substituir por uma única linha de código. É isso que fazemos na sequência.
Até lá!
Nesse vídeo, daremos continuidade no teste que estamos corrigindo.
Havíamos mencionado que conseguimos excluir as quatro últimas linhas de código expect
e substituir por apenas uma linha. Isso vai melhorar nossa manutenção, pois teremos que ajustar apenas uma linha, e também a legibilidade. Isso, porque, apenas de analisar a linha de código saberemos o que o teste está fazendo de ascensão.
Para isso, não vamos mais utilizar o método toBe
e sim o toHaveBeenCalledWith()
. Então, fazemos essa substituição na linha 46, no primeiro expect
.
Após, excluímos as três linhas abaixo, referente aos outros expect()
. No expect()
que mantivemos, nos parênteses, mantemos apenas httpResponse
.
//Código omitido
expect(httpResponde).toHaveBeenCalledWith(201);
Assim, estamos verificando se o método de resposta foi chamado com determinado parâmetro que passaremos. No caso, o que queremos saber se a resposta possui o que enviamos, ou seja, o title
, a description
e o date
.
Para facilitar, copiamos esse trecho de código próximo das linhas 33 até a 35 e colamos nos parênteses de toHaveBeenCalledWith({})
.
//Código omitido
expect(httpResponde).toHaveBeenCalledWith({
title: "any_title",
description: "any_description",
date: "30/06/2024",
});
Se a nossa resposta tiver esses dados é porque, de fato, ele conseguiu criar. Então, executaremos o teste. Abrimos o terminal e passamos o comando npm test
.
Após se concluído, repare que tivemos um erro de match indicando que o que recebemos e o que ele esperava não estão concordando. Nesse caso, foi recebido um objeto, mas ele esperava um mock ou spy.
Isso aconteceu, pois o método toHaveBeenCalledWith
espera essas informações. Mock e spy são estruturas parecidas e deixaremos uma atividade detalhando a diferença entre eles.
Para resolver esse problema, utilizaremos o spy, o espião, que fica observando até uma determinada chamada. Nesse caso, vamos colocá-lo para observar quando o método add()
, que está dentro do AddTaskStub
, foi chamado. Além disso, também observará se os itens que definimos acima também passaram.
Faremos essa modificação na prática para entendermos melhor. Na linha acima de const addTaskController
, próximo à 38, escrevemos const addTaskStub
.
Adicionamos o sinal de igual, recortamos a linha de código 40 new AddTaskStub()
, e colamos. Após, na linha 40 passamos o addTaskStub()
.
//Código omitido
const addTaskStub = new AddTaskStub();
const addTaskController = new AddTaskController(
addTaskStub,
new ValidationStub()
);
//Código omitido
Agora, faremos esse processo de espionagem passando o método jest.spyOn()
na linha 44. Como primeiro parâmetro, passamos o objeto addTaskStub
, seguido de "add"
.
O espião está observando quando esse método for chamado. Agora, precisamos capturar a resposta desse método. Então, no início da linha 44, passamos const addSpy =
.
//Código omitido
const addSpy = jest.spyOn(addTaskStub, "add");
//Código omitido
Assim, capturamos a resposta. Quando o método add
foi chamado, nosso espião está capturando a resposta dessa chamada, que está dentro de addSpy
.
Então, na linha 48, em expect()
, apagamos o httpResponse
e passamos addSpy
. Assim, quando o addSpy
for chamado, verificaremos se tera o título, descrição e data.
//Código omitido
const addSpy = jest.spyOn(addTaskStub, "add");
const httpResponse await addTaskController.handle(httpRequest);
expect(addSpy).toHave BeenCalledWith({
title: "any_title",
description: "any_description",
date: "30/06/2024",
});
});
//Código omitido
Se for verdade, significa que conseguimos criar a tarefa. Abrimos o terminal e executamos o comando npm rest
. Feito isso, nosso teste passa.
Conseguimos fazer o mesmo comportamento com apenas com uma seção. Para isso, utilizamos o método jest.spyOn()
. Usamos essa espionagem quando queremos verificar ou alterar o comportamento de um método que está dentro de um objeto.
Mas, ainda conseguimos melhorar um pouco mais esse teste. Seguindo as boas práticas, não é responsabilidade desse teste ter que lidar com as instanciações do nosso stub
. Podemos melhorar isso criando algumas factors. Faremos isso na sequência. Até lá!
O curso Testes com TypeScript: refatoração, TDD e boas práticas possui 144 minutos de vídeos, em um total de 50 atividades. Gostou? Conheça nossos outros cursos de Node.JS em Programação, ou leia nossos artigos de Programação.
Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:
Impulsione a sua carreira com os melhores cursos e faça parte da maior comunidade tech.
1 ano de Alura
Assine o PLUS e garanta:
Formações com mais de 1500 cursos atualizados e novos lançamentos semanais, em Programação, Inteligência Artificial, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.
A cada curso ou formação concluído, um novo certificado para turbinar seu currículo e LinkedIn.
No Discord, você tem acesso a eventos exclusivos, grupos de estudos e mentorias com especialistas de diferentes áreas.
Faça parte da maior comunidade Dev do país e crie conexões com mais de 120 mil pessoas no Discord.
Acesso ilimitado ao catálogo de Imersões da Alura para praticar conhecimentos em diferentes áreas.
Explore um universo de possibilidades na palma da sua mão. Baixe as aulas para assistir offline, onde e quando quiser.
Acelere o seu aprendizado com a IA da Alura e prepare-se para o mercado internacional.
1 ano de Alura
Todos os benefícios do PLUS e mais vantagens exclusivas:
Luri é nossa inteligência artificial que tira dúvidas, dá exemplos práticos, corrige exercícios e ajuda a mergulhar ainda mais durante as aulas. Você pode conversar com a Luri até 100 mensagens por semana.
Aprenda um novo idioma e expanda seus horizontes profissionais. Cursos de Inglês, Espanhol e Inglês para Devs, 100% focado em tecnologia.
Transforme a sua jornada com benefícios exclusivos e evolua ainda mais na sua carreira.
1 ano de Alura
Todos os benefícios do PRO e mais vantagens exclusivas:
Mensagens ilimitadas para estudar com a Luri, a IA da Alura, disponível 24hs para tirar suas dúvidas, dar exemplos práticos, corrigir exercícios e impulsionar seus estudos.
Envie imagens para a Luri e ela te ajuda a solucionar problemas, identificar erros, esclarecer gráficos, analisar design e muito mais.
Escolha os ebooks da Casa do Código, a editora da Alura, que apoiarão a sua jornada de aprendizado para sempre.