Como construir e testar uma API em Java utilizando o Postman

Há algum tempo o uso de APIs REST ( Representational State Transfer) faz parte da minha realidade profissional, no inicio foi um desafio idealizar e elaborar maneiras robustas de se testar estas novas funcionalidades que estávamos criando. No caso sou Analista de Testes, e vou compartilhar com vocês neste post minhas experiências com está realidade que é trabalhar com EndPoints.

É fortemente recomendado que no decorrer do post você vá efetuando os passos executados, pois assim ficará muito mais fácil de entender o que ocorre em cada etapa. Vamos dividir o escopo em três frentes:

  1. Primeiro vamos preparar o ambiente para que possamos desenvolver nossa API.
  2. Vamos realizar o desenvolvimento da API, conceituando cada elemento e entendendo sua estrutura.
  3. Vamos escrever os testes da nossa API utilizando o Postman.

Preparação para o Desenvolvimento da API

Para o desenvolvimento do projeto eu utilizei o Spring Tool Suite, mas você pode utilizar a IDE que mais lhe agradar, vou deixar abaixo um link para a instalação tanto do STS (Spring Tool Suite) quanto do Eclipse.

STS: https://spring.io/tools/sts/all

Eclipse: http://www.eclipse.org/downloads/packages/eclipse-ide-java-ee-developers/oxygen2

Eu utilizei o Maven para o gerenciamento das dependências do meu projeto, caso não esteja familiarizado com o Maven é muito interessante que leia um pouco sobre ele. Para continuar certifique-se de ter o Java e o Maven instalados.

Nós iremos construir uma aplicação muito simples, que deve ser capaz de cadastrar as viagens que o usuário vai fazer ou já fez, bem como apresentar as viagens já feitas.

Criando o Projeto

Devemos criar um Projeto Maven (File > New > Maven Project), e então em nosso arquivo pom.xml devemos colocar todas as depedências do nosso projeto, isto é, tudo que iremos utilizar para nos apoiar no desenvolvimento deve estar declarado neste arquivo, então o Maven se encarrega de fornecer estas bibliotecas.

No caso o arquivo pom.xml deve estar da seguinte forma:

https://gist.github.com/AntonioMontanha/05904c382130509ec4024ea330d5fe6d.js

Nas dependências contidas no pom.xml acima, temos desde as dependências do próprio spring boot, até outras relativas ao banco de dados que utilizaremos, no caso será utilizado um banco H2, que é um banco em memória, ou seja os dados se perdem quando nossa aplicação sair do ar, ele é ótimo para demonstrações e testes de aplicações, que é o nosso caso.


O Começo de um Projeto Spring Boot

A primeira classe que iremos criar será responsável por iniciar o nosso sistema, isto é colocar nosso Serviço em operação. Para isso eu criei uma package com.montanha.gerenciador, e dentro dela foi criado uma classe GerenciadorViagensMontanhaApplication, ficando então na seguinte estrutura (estás outras packages serão criadas posteriormente, mas se atende a utilizar esta estrutura):

Estrutura do Projeto

Nesta classe estará contido o método que iniciará o projeto, ele será um método main porém irá conter a annotation @SpringBootApplication, como mostra o código abaixo.

https://gist.github.com/AntonioMontanha/b10ce028bffd6ffd82f3c61a660ae40b

Para executar a classe acima, basta clicar com o botão direito sobre o método main e selecionar “Run as > Java Application” com isso a aplicação já estará no ar, porém ela não possui nenhum EndPoint que faça algo útil ainda.

O nosso sistema, devido as dependências que referenciamos no pom.xml, tem a capacidade de subir um Tomcat acoplado quando executamos nossa aplicação, o que nos economiza todo o trabalho de ter que configurar um servidor de aplicação. Este nosso Tomcat sobe por padrão no endereço http://localhost:8080, caso precise mudar a porta que ele esta utilizando basta no caminho src/main/resources criar um arquivo chamado application.properties e dentro dele colocar a seguinte configuração: server.port=8089, onde neste caso estaremos utilizando a porta 8089.

Entidades

A primeira parte que iremos abordar do nosso projeto serão as Entidades, a nível de organização é interessante criar uma package em que ficarão todas as Entidades do nosso sistema, no caso eu criei uma package chamada com.montanha.gerenciador.entities e dentro dela será criada a classe Viagens, que ficará da seguinte forma:

https://gist.github.com/AntonioMontanha/d28c153f3c32bba03a27143db8a4585f

Sobre a classe acima é interessante entender algumas coisas, várias das anotações utilizadas aqui, como o @Entity, @Column e o @Table são utilizadas para dizer ao JPA que esta classe será uma entidade no banco de dados, bem como orientar todo o gerenciamento de como esta classe e seus atributos são transformados em tabelas e colunas no banco.

No caso o @Component apenas diz que está classe será gerenciada pelos containers do Spring, um pouco mais para frente isso se tornará mais claro quando entendermos o que é cada componente. O @JsonFormat utilizado é simplesmente para formatar os campos de data no momento de retorna-los.

Controladores

Os controladores ou Controllers são onde estão mapeados os EndPoints da sua aplicação, eles são a porta de entrada da aplicação quando alguém deseja utilizar o serviço que foi desenvolvido. Por exemplo se você acessar um caminho hipotético http://servico-qualquer/cadastro , pelo nome da a ideia que se deseja fazer um cadastro de algo, a responsabilidade do controlador é simplesmente dizer aos outros componentes da aplicação que tem alguém querendo realizar um cadastro, e então ele simplesmente chama estes componentes para realizar a operação proposta. O Controlador não deve ter responsabilidades de processar ou persistir dados.

No nosso caso eu criei um controlador chamado GerenciadorViagemController, seguindo a mesma ideia do Entidade, criei uma package com.montanha.gerenciador.controller .

Nosso controlador para o gerenciador de viagens ficou da seguinte forma:

https://gist.github.com/AntonioMontanha/efa987d556d41776397dbb6aa3b7a4b7

Primeiro vamos nos atentar para a anotação @RestController, que faz com que o Spring entenda que está classe é um Controlador. Também é importante entender a segunda anotação @RequestMapping(“/api/viagens”), ela infere que quando alguém acessar o caminho http://localhost:8089/api/viagens a requisição irá direto para este controlador em questão.

Como foi dito anteriormente o Controlador conversa com alguns outros componentes para resolver o problema que o EndPoint acessado se propõe a resolver, então aqui temos bastante coisa para entender.

Vamos analisar o começo de nossas classe GerenciadorViagemController e entender o que cada coisa faz.

Vamos começar com este primeiro bloco:

Injeção de Dependência

Nesse momento estamos Injetando nossa classe ViagemServices em nosso controlador através da anotação @Autowired, isso se chama Injetar Dependência, por enquanto basta entendermos que fazendo isso poderemos utilizar alguns métodos de nossa classe ViagemServices. Agora vamos entender quais responsabilidades terá esta nossa clase de Serviço.

Serviços

Vamos conferir como está nossa classe de Serviço:

Como se pode perceber ela também tem sua Anotação específica, o @Service, o que faz dela uma classe de Serviço, e será nela que iremos implementar nossa regra de negócio que irá “servir” o nosso controlador.

É possível já verificar alguns métodos que criamos como o listar e o salvar, porém também podemos perceber que dentro desta classe estamos utilizando a Injeção de Dependência (@Autowired) de um outra classe, a ViagemRepository, bem como também estamos utilizando alguns métodos dessa ViagemRepository em nossos métodos listar e salvar. Embora seja na camada de Serviço que ficará a implementação da regra de negócio, não é aqui que será feito o relacionamento com o banco de dados, seja para inserir, recuperar ou apagar dados. E é exatamente por isso que injetamos a classe ViagemRepository, pois é responsabilidade do Repositório fazer essa comunicação com o banco de dados.

Porém, antes de entrarmos no conceito do Repositório mais a fundo, gostaria de chamar a atenção para uma situação que esta acontecendo no método salvar, ele não esta recebendo um objeto Viagem como parâmetro, ele esta recebendo um ViagemDto. Então vamos entender o que é esse DTO.

Entendendo um DTO

Um DTO ou Data Transfer Object, é um padrão de projeto utilizado como próprio nome já diz, para transferência de dados. No nosso caso nós criamos uma classe chamada ViagemDto, que ficou da seguinte forma:

Este Dto será responsável por transferir as informações vindas da requisição, seja um usuário fazendo um cadastro em uma tela ou dados vindos de uma outra API, estas informações irão passar pelo DTO, que passará por todas nossas camadas de Controladores, Serviços e Repositórios.

As classes ViagemDto e Viagem estão praticamente iguais, dentre as poucas diferenças encontradas estão algumas validações que podemos encontrar em nosso Dto como por exemplo o @Lenght e o @NotNull, que estipularão por exemplo os tamanhos mínimos e máximos de uma certa informação ou indicará a obrigatoriedade de um certo campo para o cadastro desta Viagem, impedindo que dados inconsistentes alcancem o banco de dados.

Pode parecer redundante utilizar um Dto tão parecido com a nossa classe de Entidade, porém, se precisássemos incluir um campo que guardasse a data que o cadastro foi feito. Este campo, deveria armazenar o exato momento que a informações foi salva, porém não é o usuário que iria informar este dado, ou seja não teríamos esta informação da requisição. Logo esta é uma informação que estaria somente em nossa Entidade Viagem, porém não estaria em nossa classe ViagemDto.

Então sabemos que nessa estrutura, quando enviarmos algo para nossa aplicação, esse será transformado em um ViagemDto, e somente no momento de salvar ele será transferido para um Viagem.

Repositórios

Os repositórios são os responsáveis pelas interações com o banco de dados.Se analisarmos, em nossa classe ViagemService apresentada acima, veremos que os métodos listar e salvar chamam os métodos findAll() e save(), acessando os mesmo através de uma instância da nossa classe ViagemRepository.

Ou seja, em nossa classe de serviço temos a seguinte situação:

Temos um método salvar que instância uma Viagem, passa todas as informações que estava em nosso Dto (aqui fica evidente o nome Data Transfer Object) para esta nova variável, e então salva as informações no banco fazendo uso do método save do nosso viagemRepository.

Porém agora veremos algo interessante, abaixo segue nossa classe ViagemRepository:

Sabemos que fizemos uso dos métodos save e findAll quando utilizamos nosso ViagemRepository em nossa classe de serviço. E estes métodos não se encontram em nossa classe de repositório.

Isso acontece porque nossa classe ViagemRepository está estendendo a classe JpaRepository ( verifique na linha 8 da imagem acima), e como estamos referenciando nossa classe Viagem para este repositório do JPA (JpaRepository), ele se encarrega de nos fornecer inúmeros métodos que acessam o banco de dados sem precisarmos implementa-los, como salvar uma viagem, retornar as viagens cadastradas, entre outros. Caso seja necessário um método que faça algo diferente do já fornecido pelo JpaRepository, podemos implementar na nossa classe VIagemRepository e usa-los normalmente.

Voltando ao Controlador

Agora que temos claro que em nossa aplicação, a camada de controle chama a camada de serviço para realizar algumas operações da regra de negócio, e esta camada de serviço chama a de repositório para interagir com o banco de dados, podemos então abordar o que nossa aplicação irá fazer e o que ela retornará para o usuário.

Tomaremos de exemplo o método cadastrar

A anotação @PostMapping identifica que o método que estamos criando é do tipo POST, caso não conheça o termo de uma olhada nos métodos HTTP aqui, você vera que o método POST é utilizado normalmente para inserir dados, e é o que faremos.

Na frente da nossa anotação temos path = “/new”, isso significa que para acessar este método que iremos construir devemos adicionar este /new ao caminho que tínhamos quando acessamos este controlador, ficando da seguinte forma :http://localhost:8089/api/viagens/new.

Para entender o que esta acontecendo no método vamos abordar rápidamente algumas anotações e parâmetros do nosso método:

ViagemDto: é o objeto que esta sendo passado para nosso método, logo é o que será cadastrado.

@Valid: lembra daquelas validações que colocamos em nosso ViagemDto, em relação a tamanho e obrigatoriedade? Está anotação verifica se o objeto ViagemDto passado atende todas as validações. Então vincula o resultado a variável result, que será utilizada para verificar se temos erros e apresentá-los no retorno no método (linha 10 até 13).

@RequestBody: ele habilita a deserialização automática do nosso objeto ViagemDto. Então se passarmos para o método um JSON da seguinte forma:

esta anotação consegue de forma simples transformar estes dados em um objeto ViagemDto.

Até agora sabemos como acessar nosso método, passar as informações que serão salvas e como ele faz para salvá-las. Então por fim vamos entender como ele responde quando ele é chamado. O que passamos para ser cadastrado, chamamos de RequestBody, ou seja é o corpo da requisição, o que será retornado chamamos de ResponseBody, analogamente, o corpo da resposta.

Response

A resposta dada por uma API possui alguns elementos, neste momento vamos dar atenção a dois deles: o body e ao status, podemos ver os dois no exemplo de resposta a baixoÇ

Exemplo de Response

O Body como podemos ver retorna informações de um viagem, logo entenderemos o código responsável por essa consulta. Acima no canto direito, podemos ver o Status: 200 OK, o 200 representa que foi possível realizar uma consulta ou um processamento com a requisição, enquanto o 201 irá indicar este mesmo sucesso, porem quando estamos persistindo dados no banco de dados.

O corpo da resposta que é retornado depende de como implementamos cada método, por exemplo, vamos usar os métodos cadastrar e o listar como exemplo, temos abaixo somente um pedaço dos métodos, mas se atende para o que cada um destes métodos esta retornando:

Método POST cadastrar
Método GET listar

Nosso método cadastrar retorna ResponseEntity<Response<Viagem>>, enquanto o listar retorna ResponseEntity<List<Viagem>>, o cadastrar irá retornar a viagem que ele acabou de cadastrar, e o listar trará as todas as viagens cadastradas no sistema, porém no primeiro o retorno esta sendo encapsulado por uma classe Response ( olhe o Response<Viagem> )enquanto no segundo não temos este encapsulamento. Isso irá mudar a estrutura do retorno em cada um destes métodos, vamos dar uma olhada na nossa classe Response e depois olhar como ela altera esses retornos.

Nossa classe response é bem simples, porém ela utiliza de um recurso do Java conhecido como Generics, logo na nome da classe podemos ver Response<T>, isso indica que nossa classe Response pode receber qualquer tipo de classe, neste caso estamos utilizando nossa classe Response para alterar a response quando estamos falando de viagens, mas se o código crescer, esta nossa classe Response continuará funcionando para as futuras implementações, pois ela foi programada para receber qualquer classe.

No caso nossa classe Response consegue agrupar nossas informações que queremos retornar no atributo data(linha 8), alem de também agrupar os erros em uma outra lista. Abaixo segue a diferença do retorno do método cadastrar e listar, onde somente o primeiro esta fazendo uso da classe Response:

Resposta utilizando a classe Response.
Resposta sem utilizar a classe Response

Como o retorno é feito

Para finalizar a parte do desenvolvimento vamos realizar uma rápida analise nos dois métodos tratados acima para entender como acontece o retorno deles, primeiro o listar:

O listar é mais simples, depois de já ter conseguido carregar as viagens cadastradas utilizando viagemService.listar() simplesmente utilizamos o ResponseEntity que é um componente do Spring para lidarmos com os elementos da resposta da API, no caso acima, setamos o Status na hora de retornar como ( .status(HttpStatus.Ok)) que no caso irá retornar aquele status 200 Ok que abordamos acima e também dizemos que o body da resposta deve ser a lista de viagens recuperadas com o método listar (body(viagens)).


O Teste da nossa aplicação

Agora iremos finalizar com como testar esta nossa aplicação, é claro que poderíamos ter escrito testes já no Java, poderíamos ter desenvolvido com TDD, mas este não é o foco deste Post, aqui iremos utilizar o Postman, uma ferramenta do Google muito boa para testes de API, ele pode ser baixado no seguinte link: Postman Download.

Com o Postman instalado vamos entender como montar testes para testar nossa aplicação, primeiro de tudo vamos clicar no botão New (canto esquerdo superior) e selecionar Collection, vamos dar o nome GerenciadorDeViagem para ela. A Collection irá agrupar todas as nossas requisições e nestas requisições que iremos escrever nossos testes.

Utilize a imagem e o glossário abaixo como um guia para entender o que é cada coisa e onde ela fica localizada na ferramenta.

Definições Postman
  1. Collection: ela irá agrupar as pastas e as requisições.
  2. Folder: é realmente uma pasta, se utiliza para organizar as Requests.
  3. Request: são as requisições, se trata do que realmente será enviado para nossa API, ela possui alguns componentes que serão abordados abaixo.
  4. Verbo HTTP: o verbo que a requisicões está utilizando, muda conforme o que a requisição deseja fazer, normalmente temos: POST, GET, PUT e DELETE.
  5. URL: é o caminho que a requisição será enviada. Lembra dos caminhos definidos em nosso ViagemController?
  6. Environment: é o ambiente que está sendo utilizado, por enquanto entenda que ele pode ser usado como um conjunto de variáveis.
  7. Body: o corpo da request, aqui serão enviadas as informações que nosso método cadastrar (POST) irá utilizar para inserir viagens no sistema.
  8. Pré -request Script: são script que você pode escrever que serão executados antes da requisição ser enviada, por exemplo, você pode escrever algo que gere um CPF de forma randômica e atribuir em uma variável, então utilizar esta variável na requisição.
  9. Tests: aqui é onde serão escritos nossos testes.

Antes de criarmos as requisições vamos criar algumas pastas dentro da nossa Collection para que possamos organizar nossas requisições, neste caso cria nos três pontos ao lado da Collection e selecione Add Folder, a primeira pasta que vamos criar se chamará Validações de Tamanho de Campo, o nome já diz que tipo de testes faremos aqui.

Por fim vamos agora criar as requisições dentro desta pasta. Clique novamente no botão New em que criamos a Collection e selecione Request, vamos chamá-la de Informando Local de Destino com menos de 3 caracteres, salve ela na pasta criada acima. Como o nome já diz iremos testar se nossa aplicação está validando um valor mínimo para Local de Destino.

Nessa nossa nova Request vamos alterar o Verbo dela para POST, e vamos colocar a URL que irá acessar o caminho de nossa aplicação que cadastra viagens, no caso: http://localhost:8089/api/viagens/new .

Temos então que informar um Body, que são as informacões para serem inseridas, clique em Body.

Selecione raw, para uma melhor visualização dos dados, onde está Text selecione JSON, isto criará uma variável em nossos Headers, dizendo que o tipo de informação que iremos trabalhar é JSON. Enfim como podemos ver temos nosso Body e nele o Local de Destino está com menos de 3 carácteres, é esperado que a nossa aplicação não insira estes dados e retorne uma mensagem de erro alertando da situação. Se clicarmos em SEND, que no caso envia a requisição para a aplicação.

Quando enviamos, tivemos o retorno esperado, nossa Response nos devolveu um Status 400, que significa que tem algo errado com a requisição, e em seu Body nos alertou quanto ao tamanho mínimo do Local de Destino.

Ok, mas e se quisermos automatizar isso? Para nunca mais precisar se preocupar com essa validação? Já pensou se todas as vezes que tivermos alguma alteração no código nós tivermos que criar novas requisições, ou então entrar em uma por uma e ir enviando para ver se nossas alterações não quebraram outras coisas? Bem não é necessário, se prestar atenção na imagem acima, de nossa Response, ao lado de Headers temos: Test Results: 2/2, ou seja dos dois testes que temos escritos para esta requisição, os dois passaram. Vamos ver estes testes então.

Na aba Tests podemos escrever os testes automatizados da nossa aplicação. Eles são feitos em JavaScript, mas não é necessário conhecer a linguagem para realizar os testes, no canto direito quando você abre a aba de Tests possuem Snippets que vão te guiar para cada tipo de teste.

Primeiro vamos verificar se o código retornado pela nossa response é 400. Então escrevemos:

tests:[“Aqui dentro escrevemos o nome do nosso teste, veja como ficou na imagem la emcima] = responseCode.code === 400

Se preferir pode acessar o Snippet “Status code: code is 200, ele irá montar um teste da seguinte forma:

Bastaria mudar para 400, e também funciona, é fácil perceber que ele utiliza uma outra estrutura para os testes, ambas funcionam, eu estou utilizando a outra por ela ser mais simples de entender em um primeiro contato, mas utilize a que você se sentir mais confortável.

Temos o teste do código da resposta feito, agora vamos fazer o da mensagem que a aplicação deve retornar. Para isso nós utilizamos o método parse, da seguinte forma:

var response = JSON.parse(responseBody);

nesse momento estamos atribuindo a uma variável response o corpo da resposta que obtivemos. O parse neste caso irá converter a string que foi retornada para um JSON, para que possamos acessar elementos específicos do JSON. Se olharmos o resposta dada pela nossa aplicação temos:

ou seja, temos um errors, que no caso é uma lista dos erros que nossa aplicação encontrou, neste caso temos um só, mas se por exemplo não informarmos Data da Partida que é uma informação obrigatória também teremos:

Então para automatizar esta validação escrevemos:

tests[“Verifica a mensagem de validação do tamanho mínimo para Local de Destino”] = response.errors[0] === “Local de Destino deve estar entre 3 e 40 caracteres”;

acima estamos utilizando aquela variável que declaramos (response), que no momento possui o JSON retornado em nossa resposta, acessando a lista de erros (.errors) e desta lista acessa o primeiro elemento dela ( [0] ), então verificar se o que está contido nesse elemento é a mensagem esperada.

Agora realmente finalizando nosso post vamos abordar o teste de um cenário de sucesso, em que conseguimos cadastrar uma viagem no sistema. Em nossa Collection vamos criar uma pasta “Validações de Regra de Negócio” e vamos criar uma request dentro dela, esta será idêntica a request que utilizamos anteriormente com a diferença que informaremos dados válidos, no caso vamos utilizar o seguinte corpo:

Com os dados informados acima esperamos que nossa viagem seja cadastrada, e esperamos em nossa resposta que a aplicação nos mostre os dados da viagem cadastrada, inclusive o id que foi atribuído a esta viagem.Então para testar esse cenário teremos a seguinte estrutura na aba Tests:

de modo que na linha 1 verificamos se a resposta foi 201; na 5 verificamos se o campo id é diferente de nulo, ou seja se foi atribuido algum id para nossa viagem; fazemos as validações dos demais campos e por fim na linha 11 verificamos se a lista de erros está vazia. O Json retornado que contemplaria os testes acima ficaria da seguinte forma:


Considerações Finais

Bem, o post ficou maior do que eu gostaria, mas espero que eu tenha conseguido passar um pouco da realidade de testes com APIs Rest, encerrando todos os passos propostos pelo nosso post já temos a capacidade de preparar e configurar um ambiente para o desenvolvimento de uma API, bem como desenvolver e testar estes nossos EndPoints.

Segue o link do repositório que se encontra o código fonte da nossa aplicação: https://github.com/AntonioMontanha/gerenciador-viagens, estou aberto para qualquer dúvida, sugestão ou crítica, até a próxima.