Desenvolvimento de APIs guiado por testes

Leonardo Vilarinho
Training Center
Published in
8 min readApr 2, 2018

Alguma vez você já se deparou com APIs que mais parecem um ninho de formigas? Onde tudo foi feito na correria e no comodismo, geralmente se tornando em uma bagunça, pois cada alteração/extensão é jogada ali sem nenhum planejameto, apenas com a velha oração para não acarretar um bug na produção.

Esse problema é cada vez mais decorrente, dado a descomplicação no processo de criação desse código que na realidade é o coração de qualquer aplicação. Eu mesmo já sofri desse mal, apenas abarrotando o novo código ali sem pensar nas consequências daquilo para a saúde da aplicação, e sim mentalizando apenas que meu frontend viria e comeria os dados que servi ali.

Apesar de sempre ter um gosto pela área de teste, era um porre ficar pensando em como executar testes em sistemas com banco de dados e rotas. Mas a solução para isso é bem mais simples do que pode-se pensar, e é isso que esse artigo aqui irá mostrar!

Inicialmente, o objetivo não é jogar um código de exemplo na sua cara e te deixar com esse conteúdo sem mais o que fazer, mas introduzir o assunto mostrando que as decisões tomadas sobre os testes escritos antes da implemetação acarretão demais para melhorar o fluxo e a arquitetura do seu sistema.

Mergulhando no assunto e na minha opinião pessoal, em testes para APIs não é legal criar mocks e testes unitários, a única modificação feita no código da aplicação será a conexão com o banco de dados, onde iremos usar um banco especifíco para testes, assim os testes serão baseados em endpoints, que trazem a vantagem de definir e testar todo o fluxo da aplicação para aquela rota.

Crie uma pasta qualquer na sua máquina e execute o comando npm init -y para criarmos o esqueleto do arquivo de dependências do NPM, após isso temos que instalar os pacotes usados no ambiente de desenvolvimento:

npm i supertest jest @types/jest nodemon -D

Em resumo: o supertest será a biblioteca responsável por disparar as requisições em um app do Express; o jest é o test runner que iremos usar, sua escolha foi um gosto pessoal, por ser fácil de instalar e configurar, com sintaxe amigável e modo watch para executar apenas testes com diff do Git; o @types/jest ajuda a deixar o autocomplete dos editores de texto uma delícia para o Jest; e o nodemon é aquele velho amigo dos desenvolvedores Node, fazendo um watch para recarregar a aplicação sempre que um arquivo de código dela é modificado.

A seguir já podemos instalar os pacotes para serem usados em produção, assim já eliminamos ficar mexendo com isso mais tarde:

npm i express body-parser mongoose lodash dotenv -S

Em mais um resumo: o express é a biblioteca famosinha para gerenciar o protocolo HTTP e outras coisas da aplicação; o body-parser faz o parseamento do corpo das requisições para facilitar pegar os dados recebidos; o mongoose é um amigo usado para facilitar o trabalho com MongoDB, dando suporte a validações, modelos e outras coisinhas; o lodash que é uma mão na roda para manipular coleções de dados e outros; e o dotenv para criar arquivos .env com variáveis de ambiente da aplicação.

Para terminar a configuração básica do projeto, temos que definir os scripts da aplicação, normalmente uso prefixos para organizar eles, pois podemos ter o back-end e o front-end no mesmo código, então uso o prefixo api :

"scripts": {  "api:dev": "nodemon source/index.js",  "api:start": "node source/index.js",  "api:test": "jest"},

Nesse artigo o problema que temos que resolver é bem simples, a startup Cabral (fictícia) ficou milionária da noite para o dia, seu CEO quer recompensar os funcionários que estão ali desde o primeiro dia, para isso está dividindo a participação da empresa entre eles, tornando-os sócios.

Nossa API irá controlar essa divisão da empresa, onde o CEO poderá inserir um novo sócio e sua participação, porém o sistema não pode deixar o CEO se enganar e distribuir mais que 100% das participações.

Dado esse escopo, a primeira coisa que devemos ter em mente é a criação do recurso, no caso temos que criar um sócio para então poder manipula-lo em outras partes da feature, logo esse deve ser nosso primeiro teste.

Mas ainda antes de codar, devemos pensar em um padrão para a API, ao definir um retorno padronizado para todos os endpoints teremos maior organização e não fritamos a cabeça do front developer. Em meus projetos utilizo essa estrutura de retorno:

{ "error": false | "<message>", "<feature>": object | array }

Onde o error indica se houve ou não um erro, caso seja false não ocorreu erro, em caso de erro ele será a mensagem do mesmo, e feature se transforma no nome do recurso que o endpoint manipulou e seu valor é o objeto de um recurso ou uma lista desses recursos. Por exemplo:

{ "error": false, "partners": [{...}, {...}] }

Com esse planejamento concluído, vamos codar! No arquivo /tests/partner.test.js vamos colocar os testes da feature dos sócios, o primeiro teste é a criação do recurso/sócio:

Veja que antes do describe tenho algumas declarações básicas, como: a importação do app (que ainda não existe); um objeto template base com os dados de um sócio; e um atalho para o caminho da rota do recurso;

No teste, temos o describe informando que todos os testes dele pertencem a feature de sócios, e no primeiro testes usamos o supertest para iniciar uma requisição post na rota /partners enviando os dados do template. Após isso, pegamos o texto e o status do retorno, veficamos se o código do retorno é 200 e o error é falso, ou seja, que não houve erros.

Ao executar o comando npm run api:test é mostrado o erro:

Note que o teste falhou pois o arquivo /source/app.js não existe, então devemos cria-lo, nele criamos uma instância do Express aplicando o bodyParser nela e exportando-a:

Ao executar os testes novamente, temos mais um erro informando que o retorno da API não é um json, isso se deve ao fato do Express retornar uma página HTML com a mensagem “Cannot POST /partners”, informando que a rota não existe.

Então o próximo passo é criar essa rota, mas antes precisamos criar o modelo do mongoose para essa feature, assim fazendo com que vinculemos partner com uma coleção do mongo, no arquivo /source/models/partner.js:

Agora no arquivo /source/routes/partners.js temos que definir a rota para criar um novo sócio, vamos criar o código apenas para o teste passar, pegando os dados do corpo da requisição, enviando para o método create do modelo para armazena-lo ao mongo, retornando a resposta de acordo com as regras que definimos:

Já estamos quase finalizando rs, precisamos criar o arquivo /source/routes/index.js para unir todas as rotas da aplicação:

E por fim, no arquivo /source/app.js importa-lo e enviar nosso app para lá:

Agora você deve estar pensando: “Tudo pronto, posso rodar esse troço!” … calma lá, é estranho que já codamos a parte do model do banco, mas nem siquer nos conectamos a ele. Essa parte é um pouco especial, teremos duas conexões de banco, uma para os testes e outra no código do servidor, então vamos definir a string de conexão das duas no arquivo /.env:

Agora temos que definir um arquivo de configuração para o jest, isso exige mais dois passos, no package.json crie a seção jest, com definindo o arquivo de configuração:

...
"jest" : {
"setupTestFrameworkScriptFile": "./tests/setup.js"},...

Agora no/tests/setup.js, temos que definir com o método beforeAll tudo aquilo que deve ser rodado antes dos testes, no caso a conexão com o banco de dados de teste, e no afterAll tudo aquilo feito após a execução dos testes, no caso a exclusão do banco de testes:

Agora sim!!! Temos a rota totalmente criada e a configuração dos testes também, vamos tentar rodar os testes?

Sucesso! Mas calma lá pessoa, o CEO deixou bem claro que ele se esquece às vezes de algumas coisas, e que o sistema deveria validar se ele colocou um percentual (entre 1 e 100) e um nome válido para o novo sócio. Isso nos dá no minímo mais quatro testes:

Note que como os testes são parecidos, só trocando o nível do percentual ou a propriedade inexistente, acabei criando métodos para testar a partir dos parâmetros informados, poupando um pouco de código duplicado. Assim podemos testar vários percentuais usando apenas uma linha, como é o caso, estou testando com 0, -1 e 101.

É possível perceber que ao executar os testes os quatro falharam, já na primeira asserção o código recebido não se correspondia. Isso porque não fizemos tratamento de erros ou validações nem na model nem na rota, então vamos fazer isso!

Na model do mongoose, usamos a própria validação do mongoose para validar e retornar os erros sobre o intervalo do percentual e nome:

Esses erros de validação são disparados como exceções, então no arquivo de rotas devemos usar um try catch para capturar os erros retornados e enviar o primeiro como resposta da API:

Ao executar os testes mais uma vez obteremos sucesso, mas nosso CEO pediu mais uma coisa, lembra? O sistema deve calcular todos os percentuais dos sócios e informar caso a soma seja maior que 100%, pois ele não quer dar o que não pode para seus funcionários. Para isso, temos um novo teste:

Novamente ao executar teremos um erro de status retornado pela API, pois como não temos essa regra de negócio implementada a API criou normalmente o sócio, mesmo expirando 100% de participação. Vamos corrigir isso adicionando apenas algumas linhas dentro da rota, para realizar tal verificação:

Veja que apenas chamei a validação antes de criar o sócio paraque verifiquemos se os dados são válidos antes de unir o percentual do novo sócio com os demais para comparação, caso seja maior que 100% uma resposta com o erro é retornada.

E é só isso, ao executar os testes temos sucesso:

Note que os testes podem ser uma documetação do sistema, pois a lista de features mostra o que cada recurso é capaz de fazer e quais são as regras para ele.

Para finalizar podemos criar o arquivo /source/index.js para de fato iniciar um servidor para a API, esse é o único arquivo que não terá teste, pois só conectará com o banco da produção e iniciará o servidor:

Com isso chegamos ao fim desse artigo, espero que tenham gostado dessa visão diferente, não mostrando apenas um exemplo, mas também mostrando um pensamento e o fluxo da aplicação do TDD em uma API.

Tenho pouca experiência no mercado, então qualquer distinção com o mundo real me perdoe, porém com a pouca experiência já tenho uma noção que essa abordagem é a mais ou menos usada nas empresas. Aliás estou em busca de emprego rs caso tenha alguma indicação entre em contato, e caso queira saber mais sobre mim entre nesse chatbot e faça uma pré entrevista deixando um feedback para contato!!!

Em caso de críticas ou seguestões, sempre deixe nos comentários, estarei respondendo assim que possível. Oooops, ia me esquecendo, esse código está disponível nesse repositório, até mais!

--

--

Leonardo Vilarinho
Training Center

Analista de Sistemas pelo Instituto Federal do Triângulo Mineiro