Como criar objetos complexos para seus testes de API criando builders com Lombok

Rafael Peixoto
Revista DTAR
Published in
7 min readOct 14, 2020

--

Com o poder do Lombok de simplificar a criação de POJOs, criar dados de teste para utilizar no corpo de suas requisições HTTP se torna uma tarefa bem fácil. No meu post anterior, Rest Assured e Lombok para dados de teste, um guia prático, eu falei sobre como usar o Lombok para criar, de maneira simples e fácil, dados para seus testes de API. Agora vamos dar mais um passo. Começaremos a criar dados para objetos mais complexos.

Continuaremos usando o ServeRest nos exemplos, mas neste post usaremos o endpoint /carrinhos, que precisa de um objeto um pouco mais elaborado, ao invés do /login que só precisa de dois campos do tipo String. A seguir, um exemplo de JSON que usaremos:

{
"produtos": [
{
"idProduto": "BeeJh5lz3k6kSIzA",
"quantidade": 1
},
{
"idProduto": "YaeJ455lz3k6kSIzA",
"quantidade": 3
}
]
}

Compreendendo a estrutura do JSON

Primeiro vamos entender esse JSON. De cara, podemos observar que produtos é do tipo array, ou seja, uma lista. Sendo mais preciso, nesse caso, temos uma lista de produtos. É fácil de identificar isso, pois temos o colchete abrindo logo após o nome do campo.

Outra coisa importante é que cada item dessa lista é um objeto. Temos no exemplo anterior dois objetos do tipo Produto. E cada produto possui dois campos:

  • idProduto que é uma String;
  • quantidade que é um número inteiro.

Antes de começarmos a criar os objetos em Java, precisamos entender um pouco mais sobre o que é um Produto aqui no contexto do ServeRest. Para isso, vamos dar uma olhada em um outro endpoint importante: /produtos.

Suba o ServeRest na sua máquina local e faça um GET em /produtos. A resposta deve ser algo parecido com:

{
"quantidade": 2,
"produtos": [
{
"nome": "Logitech MX Vertical",
"preco": 470,
"descricao": "Mouse",
"quantidade": 381,
"_id": "BeeJh5lz3k6kSIzA"
},
{
"nome": "Samsung 60 polegadas",
"preco": 5240,
"descricao": "TV",
"quantidade": 49977,
"_id": "K6leHdftCeOJj8BJ"
}
]
}

O motivo de olharmos esse endpoint é que temos informações que não são exibidas no endpoint de carrinhos. Um produto tem ao todo 5 atributos: nome, preco, descricao, quantidade e _id.

Observação: o _id é gerado automaticamente no momento do cadastro de um novo Produto.

Modelando o objeto

Com todas essas informações podemos começar a modelar esse objeto no Java e, claro, usando o Lombok.

O exemplo acima cria um objeto Java que representa um Produto, porém a requisição que precisamos fazer em /carrinhos precisa de uma lista de produtos. Então como fazer? Vamos criar uma nova classe chamada RequestCarrinhoBody. Essa classe terá apenas um atributo: uma lista de produtos conforme a seguir:

Olha que legal o que acabamos de fazer: o atributo quantidade é uma String simples, sem novidades aqui. Mas o atributo produtos é uma lista. Uma lista de quê? De produtos, aquela classe que criamos anteriormente. Para incluir valores nesse objeto e usá-lo na nossa requisição, precisamos apenas usar os setters que o Lombok cria para nós.

Vamos criar uma classe de teste para o carrinho que vou chamar de CarrinhosTest. Também vamos criar um método de testes criando apenas a massa de dados por enquanto.

Com o código anterior já temos a massa de dados para inserir no body da nossa requisição. Vamos então incluir o código que faz a requisição:

Execute esse código e verá que o teste irá falhar.

O problema de valores null

Se você rodou o código anterior com certeza recebeu um 400 — Bad Request. O motivo disso a gente pode ver na saída do console (já que no exemplo anterior eu incluí o log na linha 14). O corpo da requisição enviada foi o seguinte:

Quando criamos o requestCarrinhoBody, nós não passamos o nome, preco e nem a descrição, mas ainda assim esses campos aparecem como null (ou zero no caso do preço que é do tipo int). Isso acontece porque um Produto realmente tem esses campos e quando eles não são preenchidos eles voltam com valores default. E como resolvemos isso?

Resolvendo o problema de valores default

A solução deste problema é bem simples. Vamos adicionar uma nova anotação nas classes Produto e RequestCarrinhoBody:

Agora os valores default não serão mais exibidos. Caso você insira algum valor ao campo, ele será retornado, do contrário, não. Com esse ajuste nosso código volta a funcionar.

Mas e o Builder?

O builder é um design pattern que nos ajuda na criação de objetos complexos. Não vou me estender na explicação do Builder, mas aconselho a leitura do seguinte artigo para você entender mais sobre ele:

Pense comigo, conforme nosso projeto ganha objetos mais complexos, com cada vez mais campos em um JSON e com tipos mais específicos relacionados ao domínio do seu negócio, mais difícil será controlar as massas de teste. Só de pensar nisso, já cansei! Mas é nesse cenário que o Builder pode nos ajudar e implementá-lo é bem simples.

Vamos apenas adicionar uma nova anotação nas classes Produto e RequestCarrinhoBody. Aqui vou mostrar apenas na RequestCarrinhoBody para não ficar tão extenso.

Se executarmos o código agora, vamos ter um pequeno problema.

java: constructor Produto in class models.Produto cannot be applied to given types;
required: java.lang.String,int,java.lang.String,int,java.lang.String
found: no arguments
reason: actual and formal argument lists differ in length

Esse erro acontece porque uma das coisas que o Builder faz é deixar o nosso construtor padrão (o método construtor que não possui parâmetros) privado. Dessa forma, como tentamos criar a instância de Produto (new Produto()), o Java não encontra esse construtor.

Para resolver esse problema, mais uma vez temos uma anotação mágica. E para agilizar, vamos de uma vez inserir uma anotação que irá gerar o construtor padrão, mas também uma que gera um construtor que contém um parâmetro para cada um dos atributos.

Importante: É necessário incluir também na classe Produto.

Com esses últimos ajustes, os testes voltam a funcionar, mas ainda não estamos usando o builder de fato. Vamos voltar a classe CarrinhoTest e nela vamos mudar a forma como criamos o nosso objeto que será enviado no body.

Vamos entender o que foi feito aqui:

Na linha 4 é iniciado um builder de requestCarrinhoBody;
Na linha
5 é informado qual o primeiro campo que quero incluir nesse objeto. Nesse caso é uma lista de produtos. Por se tratar de uma lista, fiz o uso de Arrays.asList;
Na linha
6 é criado um segundo builder, mas agora um builder de Produto.;
Nas linhas
7 e 8 informo os campos que esse produto terá (idProduto e quantidade)
Na linha
9 o .build() é para consolidar os campos e criar o objeto Produto.
Na linha
10 temos outro .build(), mas esse agora consolida os campos para criar o RequestCarrinhoBody.

Neste exemplo, eu fiz o builder quebrado em várias linhas para facilitar o entendimento, mas perceba que, a partir da linha 4 tudo poderia ser feito numa mesma linha, pois está tudo encadeado. De qualquer forma, não recomendo deixar numa única linha para não prejudicar a leitura.

Separando os builders

Talvez você ache que ficou mais complicado agora, não é mesmo? Vamos fazer o mesmo caso novamente, só que vamos criar o builder de Produto e de RequestCarrinhoBody separadamente.

Agora temos os dois builders criados separadamente, mas o funcionamento ainda é o mesmo.

Colocando mais produtos no carrinho

Você pode estar se perguntando: "…mas e se eu precisar colocar mais de um produto no meu carrinho?" Simples, crie um novo produto e o inclua na lista.

Pronto. Na linha 10 agora incluímos dois produtos.

Listas mais legíveis com Singular

E por fim, mas não menos importante, podemos melhorar a leitura de inclusão de itens numa lista de builder utilizando o Singular. Adicione o Singular na classe RequestCarrinhoBody:

Ao contrário das demais anotações, o Singular vai funcionar apenas para um atributo e esse atributo precisa ser uma lista.

Agora ao invés de eu usar o Arrays.asList(), eu crio cada item da minha lista como um novo atributo no singular.

O Singular faz com que exista essa opção produto (não confundir com produtos). Cada produto é um item da lista. Mais fácil não é mesmo?

Conclusão

Neste artigo vimos que nem todos os objetos do nosso dia-a-dia serão simples, mas que podemos facilitar a criação deles usando todo o poder do Lombok. Aprendemos que o Builder permite a criação de objetos de uma forma fácil e bem legível e o Singular ajuda na inclusão de itens numa lista.

Ainda vimos que atributos que não definimos valor são criados no nosso objeto com valores default (null ou zero) e se não quisermos usá-los, isso pode ser tratado com o @JsonInclude(JsonInclude.Include.NON_DEFAULT).

O código completo você confere em https://github.com/rapesil/rest-assured-e-lombok.

O próximo passo é fazer com que a criação dos dados de teste não seja feita diretamente na classe de teste, mas isso fica para um próximo post.

Curtiu? Tem alguma dúvida? Comenta aí e vamos trocar uma ideia.

Até Mais!

Referências

Agradecimentos

Muito obrigado Bruno Pulis, Eduardo Misael e Gabriel Santos pela ajuda na revisão deste conteúdo.

--

--