APIs e Microsserviços

22 fev, 2018

Testes de contrato de API

Publicidade

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

***

Este artigo foi publicado originalmente em: https://www.concrete.com.br/2018/02/14/testes-de-contrato-de-api/