A crescente busca para garantir a qualidade da aplicação que está sendo desenvolvida, faz com que tenhamos a necessidade de aplicar diferentes tipos de testes em nossa aplicação e implementar cada teste em sua respectiva camada. E para poder identificar qual camada melhor se aplica ao teste, podemos utilizar a conhecida pirâmide de testes.
Neste artigo, iremos tratar de um tipo de teste voltado à aplicações com arquiteturas baseadas em serviços e microsserviços. Vamos falar de Teste de Contrato de API. Mas fique tranquilo, não vem “textão” por aí, afinal, nosso objetivo é hands-on.
Apenas para contextualizar, vou passar brevemente pela parte teórica antes de colocarmos a mão na massa.
API
- API é um conjunto de rotinas e padrões de programação para acesso a um aplicativo de software ou plataforma, baseado na Web;
- A sigla API refere-se ao termo em inglês “Application Programming Interface”, que significa em tradução para o português: “Interface de Programação de Aplicativos”.
Teste de API
- Estão entre a camada de testes de UI e Unitários;
- Podem ser automatizados em paralelo com o desenvolvimento da API;
- Facilitam a validação de múltiplos cenários;
- Garantem que a estrutura do JSON de retorno esteja correta.
Payload
- É todo o conteúdo enviado por um meio de transporte. É o corpo da informação, o que é útil de tudo o que está sendo transmitido;
- Não possui um formato obrigatório. O fato de ser JSON é apenas circunstancial.
Response
- É todo o conteúdo recebido por um meio de transporte; é o corpo da informação de retorno;
Contrato de API
- Tem como objetivo garantir que o conteúdo fornecido não tenha sido modificado, podemos dizer que tem a finalidade de validar se o contrato acordado foi ou não quebrado; deve validar se o schema permanece o mesmo garantindo assim a integridade dos dados na comunicação entre client/server;
- É possível validar se os dados continuam do mesmo tipo, como os valores limites, a restrição de valores recebidos, se a estrutura foi ou não modificada, etc.
Como falei no início que seria mão na massa, bora codar!
Vamos utilizar o amado e odiado JavaScript, mas, para isso temos que preparar o nosso ambiente. Vou me restringir ao ambiente macOS, caso esteja utilizando um sistema operacional diferente, dá uma “googlada” e veja como instalar as dependências em seu SO.
$ brew update $ brew install node - $node -v $ npm install --global mocha $ npm install supertest $ npm install chai $ npm install joi $ npm install joi-assert
Agora, para dar aquele confere, veja se o Node foi instalado corretamente. No meu caso estou utilizando a versão 7.x.
$ node --version v7.9.0
Como a repetição é a melhor forma de aprender, não copie e cole os códigos abaixo, deixe a preguiça de lado e escreva você mesmo o código.
Criamos um mock que consiste em informações sobre um filme, que vai retornar o seguinte objeto:
{ "Title": "Guardians of the Galaxy Vol. 2", "Year": 2017, "Language": "English", "Ratings": [{ "Source": "Rotten Tomatoes", "Value": "83%" }, { "Source": "Metacritic", "Value": "67/100" }], "Type": "movie", "Production": "Walt Disney Pictures", "Website": "https://marvel.com/guardians", "Response": true }
Basicamente, precisamos criar dois arquivos. Um chamado request.js e outro chamado schema.js.
request.js
'use strict'; const Joi = require('joi'); const request = require('supertest'); const expect = require('chai').expect; const joiAssert = require('joi-assert'); const { schemaFilmeValido, schemanheritingRating } = require('./schema'); const URL = 'http://www.mocky.io/'; const PATH = 'v2/5a5cb3872e00005e199f83db' describe('Teste Contrato API', function () { it('Validando response com joiAssert', function (done) { request(URL) .get(PATH) .expect('Content-Type', /json/) .end(function (err, res) { expect(res.status).to.be.eql(200); joiAssert(res.body, schemaFilmeValido); done(err); }) }); /* abortEarly: true. Para a validação assim que ocorre o primeiro erro, abortEarly: false. Retorna todos os erros encontrados no schema. Por default esse tag é true */ it('Validando response com Joi.validate', function (done) { request(URL) .get(PATH) .expect('Content-Type', /json/) .end(function (err, res) { expect(res.status).to.be.eql(200); Joi.validate(res.body, schemaFilmeValido, { abortEarly: false }, (err, data) => { if (err) throw err; }); done(err); }) }); it('Validando response utilizando schema conjugado', function (done) { request(URL) .get(PATH) .expect('Content-Type', /json/) .end(function (err, res) { expect(res.status).to.be.eql(200); Joi.validate(res.body, schemanheritingRating, { abortEarly: false }, (err, data) => { if (err) throw err; }); done(err); }) }); });
schema.js
'use strict'; const Joi = require('joi'); /*Neste exemplo, montamos o schema com todas as propriedades do objeto que recebemos.*/ const schemaFilmeValido = Joi.object({ Title: Joi.string().required(), Year: Joi.number().integer().positive().required().options({ convert: false }), Language: Joi.string().valid('English').required(), Ratings: Joi.array().items(Joi.object({ Source: Joi.string().required(), Value: Joi.string().required(), })), Type: Joi.string().valid(['movie', 'series', 'cartoon']).required(), Production: Joi.string().optional().allow('', null), Website: Joi.string().required(), Response: Joi.boolean().required(), }).required(); /*Caso tenhamos um microserviço de rating podemos criar um schema único para ele que depois importaremos esse schema para dentro do schema de Filmes.*/ const ratingSchema = Joi.object({ Source: Joi.string().required(), Value: Joi.string().required(), }) /*É possível conjugar múltiplos schemas sem a necessidade de duplicar codigos no exemplo abaixo, importamos o schema ratingSchema. Essa estrutura é util quando possuímos vários micro serviços que irão compor um único serviço final e precisamos tbm implementar testes para esses micro serviços.*/ const schemanheritingRating = Joi.object({ Title: Joi.string().required(), Year: Joi.number().integer().positive().required().options({ convert: false }), Language: Joi.string().valid('English').required(), Ratings: Joi.array().items(ratingSchema), Type: Joi.string().valid(['movie', 'series', 'cartoon']).required(), Production: Joi.string().optional().allow('', null), Website: Joi.string().required(), Response: Joi.boolean().required(), }).required(); module.exports = { schemaFilmeValido, schemanheritingRating }
Conforme o código acima, temos as seguintes validações:
- Joi.string().required() – String e Obrigatório;
- Joi.number().integer().positive().required() – Inteiro, Positivo e Obrigatório;
- Joi.string().valid(‘English’).required() – String, onde só iremos aceitar o valor ‘English’ e esse campo vai ser obrigatório;
- Joi.string().valid([‘movie’, ‘series’, ‘cartoon’]).required() – Essa opção é semelhante à mostrada acima, porém, ao invés de apenas uma opção no valor, vamos informar uma lista de possíveis valores;
- Joi.string().optional().allow(”, null) – Informamos que a propriedade é opcional e é permitido vazio;
- Response: Joi.boolean().required() – Por fim, temos a opção boolean, na qual só vamos aceitar valores true ou false.
Agora que escrevemos os cenários e os contratos, vamos rodar:
$ mocha request.js
Se tudo estiver certo, o resultado vai ser como esse abaixo:
E assim concluímos uma implementação inicial de testes de contrato de API. Fique à vontade para manipular o contrato e validar as opções não demonstradas neste artigo.
Referências
- GitHub
- NodeJS
- supertest
- chai
- Mocha
- JOI
- JOI-Assert
- http://www.restapitutorial.com/httpstatuscodes.html
***
Este artigo foi publicado originalmente em: https://www.concrete.com.br/2018/02/14/testes-de-contrato-de-api/