Skip to content

saymowan/cypress-api-testing

Repository files navigation

🚀 Cypress 4 test automation api 🚀

Cypress 4 test automation api Code Quality Badge ServeRest Cypress.io

Projeto para estudo e definição de uma arquitetura base para testes automatizados de API Rest com Cypress.

✊ Uso deste material


  • Seja maneiro (a), faça referência ao utilizar esta arquitetura/repositório ✌️;

✨ Instalação e uso da arquitetura


  • Instale o Node.js;
  • Baixe este repositório ou faça um git clone;
  • Abra o diretório do projeto e execute o comando:
    • npm install
  • Para abrir a interface de execução do Cypress, execute no diretório do projeto:
    • npx cypress open
  • Próximo passo é configurar/instalar a API ServeRest na sua máquina;

✨ Instalação API ServeRest


⚙️ Arquitetura do projeto


cypress4testautomationapi/
  ├─  cypress/
  │        │
  │        ├── fixtures/
  │        │   ├── *.json
  │        │   ├── *.csv       
  │        │   └── *.png
  │        │
  │        ├── integration/
  │        │   ├── <categoria>/
  │        │   │   └── <categoria>Tests.spec.js
  │        │   └── <categoria2>/
  │        │       └── <categoria2>Tests.spec.js
  │        │
  │        ├── plugins/
  │        │   └── index.js
  │        │
  │        ├── reports/
  │        │   └── mocha/
  │        │         └── mochafiles (*.json, *html)
  │        │
  │        ├── support/
  │        │   ├── databaseCommands.js
  │        │   ├── apiGeneralCommands.js
  │        │   ├── api<Categoria>Commands.js
  │        │   ├── api<Categoria2>Commands.js
  │        │   └── index.js
  │        │  
  │        └── videos/
  │ 
  ├── environmentsConfig/
  ├── node_modules/
  ├── cypress.json
  ├── package-lock.json
  ├── package.json
  └── README.md

🔍 Camadas da arquitetura

  • fixtures: arquivos para massa de dados estática para os testes (csv, png, xlsx, txt);
  • integration: arquivos de testes separados em categorias/módulos da API para facilitar a organização. Extensão *.spec.js;
  • plugins: plugins que são utilizados na solução ficam dentro do arquivo "plugins/index.js";
  • reports: diretório com o relatório de execução dos testes usando Mocha Awesome;
  • support: camada com comandos Cypress customizados e sobrescritas globais:
    • Mapeamento das requisições (headers, requestservice, parametros [body, path, queryString]) para reuso em diferentes testes.
    • Arquivo para comandos de select/insert em banco de dados.
    • Arquivo index.js responsável de receber as importações dos comandos Cypress;
  • videos: geração opcional de videos das execução dos testes;
  • environmentsConfig: diretório com os arquivos de configuração por ambiente;
  • node_modules: arquivos ou diretórios que podem ser carregados pelo Node.js;
  • cypress.json: arquivo de configuração do Cypress;
  • package-lock.json: gerado automaticamente com as instalações e atualizações de pacotes;

💡 Features


Requests como commands Cada endpoint é mapeado com a sua estrutura (headers, parâmetros, método, endpoint, cookies) no Cypress commands para focarmos em reuso. Os arquivos de mapeamento de requisições podem ser feitos por módulo/categoria. Exemplo:

Exemplo requisição

No exemplo vemos o mapeamento do endpoint Produtos para ser usado por todos os testes de API que desejam utiliza-lo. Para criar um teste com esta requisição basta utilizar o command referente e passar o(s) parametro(s):

    it('Produtos - Buscar Produto Inexistente', ()=>{
        cy.getProdutos('nome=9dj9128dh12h89')
            .then(response =>{
            expect(response.status).to.equal(200)
            expect(response.body.quantidade).to.equal(0)
        })
    })
Testes isolados e e2e Testes de requisição de maneira isolada para validar parâmetros válidos, inválidos, status code estão presentes nesta arquitetura:
    it('Produtos - Excluir Produto Inexistente',()=>{

        cy.deleteProdutos("xxx", true)
            .then(response =>{
                expect(response.status).to.equal(200)
                expect(response.body.message).to.eq("Nenhum registro excluído")
            })            
    })

Testes de múltiplas requisições (e2e) podem ser feitos com esta arquitetura, veja exemplo de um teste para Deletar um Produto (produto é criado durante o teste):

it('Produtos - Excluir Produto Existente',()=>{

    const produto ={
        nome: faker.random.uuid(),
        preco: faker.random.number(),
        descricao: "Mouse bom",
        quantidade: "5"
        }

    cy.postProdutos(produto)
        .then(response =>{
        expect(response.status).to.equal(201)
        expect(response.body.message).to.equal("Cadastro realizado com sucesso")
        let _id = response.body._id

            cy.deleteProdutos(_id, true)
                .then(respDelete =>{
                    expect(respDelete.status).to.equal(200)
                    expect(respDelete.body.message).to.eq("Registro excluído com sucesso")
                })   

                cy.getProdutos('_id='+_id)
                .then(respGet =>{
                    expect(respGet.status).to.equal(200)
                    expect(respGet.body.quantidade).to.equal(0)
                })              
            })
    })
Testes de exceção de status code (4xx e 5xx)

Para testes de exceção de status code (client side [4xx] or server side [5xx]) precisamos incluir um parâmetro failOnStatusCode na requisição com valor false.

Vide exemplo de mapeamento de requisição:

Cypress.Commands.add('deleteProdutos', (productId, failStatusCode) =>{
    cy.api({
        method: 'DELETE',
        url: '/produtos/'+productId,
        headers: {  Authorization : localStorage.getItem('token') },
        failOnStatusCode: failStatusCode
    })
})

Vide exemplo de teste "forçando" um erro para validar o statuscode e response body:

    it('Produtos - Excluir Produto token expirado',()=>{
        localStorage.setItem('token', "token erradinho")

        cy.deleteProdutos("xxx", false)
            .then(response =>{
                expect(response.status).to.equal(401)
                expect(response.body.message).to.eq("Token de acesso ausente, inválido, expirado ou usuário do token não existe mais")
            })            
    })
Mock de dados

Biblioteca Faker para mock de dados. Vide exemplos de dados que podem ser mascarados.

Data Driven Testing

A arte de reaproveitar o mesmo teste com o mesmo fluxo e asserção variando somente a massa de teste proveniente de dados estáticos ou arquivos (*.csv, *.json, *.xlsx), chamamos de Data Driven Testing (leia mais sobre), na arquitetura temos o uso de um arquivo json (JArray) para a massa de testes:

[
    {
        "nome": "Mouse Gamer Adamantiun Shinigami Usb",
        "preco": 98,
        "descricao": "Mouses para Jogos",
        "quantidade": 12
    },
    {
        "nome": "Monitor Gamer AOC Agon 32'' Curvo 165Hz",
        "preco": 269,
        "descricao": "Monitores Gamer",
        "quantidade": 45
    },
    {
        "nome": "Kit 3 Roteadores Gigabit Wifi TP-Link Rede Mesh AC1200",
        "preco": 189,
        "descricao": "Dispositivos de Conexão em Rede",
        "quantidade": 78
    }
]

O mesmo teste é criado N vezes através do arquivo json:

const produtos = require('../../fixtures/Produtos/produtosList.json')
const faker = require('faker')

  //JArray (produtoList.json) com cada objeto a ser cadastrado
  produtos.forEach(produto => {
  it('Produtos - Cadastrar Produto DDT',()=>{

      let expectedStatusCode = 201;
      let expectedSuccessMessage = "Cadastro realizado com sucesso";

      const produtoTestData ={
          "nome": produto.nome + "-" + faker.random.number(),
          "preco": produto.preco,
          "descricao": produto.descricao,
          "quantidade": produto.quantidade
        }

      cy.postProdutos(produtoTestData)
          .then(response =>{
          expect(response.status).to.equal(expectedStatusCode)
          expect(response.body.message).to.equal(expectedSuccessMessage)            
      })
  })
})
Mocha report customizado

Em desenvolvimento

Chai: asserção status code e response body

Podemos validar de diversas formas os dados retornados no response (body, cookies, headers, status code), vide exemplos que podem ser aplicados: clique aqui.

Exemplo de assertiva de status code e parâmetro "message" do response body com o método "to.equal":

    expect(response.status).to.equal(expectedStatusCode)
    expect(response.body.message).to.equal(expectedSuccessMessage)            
Orquestração de métodos

A organização dos métodos que devem ser executados antes ou depois dos testes ou bateria pode ser feito através de méetodos nativos do Cypress, clique aqui para detalhes.

Um exemplo comum para testes de API é a geração de token de acesso a cada teste, veja exemplo abaixo do método que é executado antes de cada teste para garantir o acesso dos recursos com o token correto:

    beforeEach(() => {
        cy.generateTokenAsAdmin()
    })

Neste caso, o Token é gerado como admin e usamos a Request as Command (../support/apiGeneralCommands.js), veja a requisição mapeada e já enviando o token para o storage para ser usado por todos os testes no header:

Cypress.Commands.add('generateTokenAsAdmin', () =>{
    cy.api({
        method: 'POST',
        url: '/login',
        body: {
            "email": "fulano@qa.com",
            "password": "teste"
          }
    })
    .then(response =>{
        expect(response.status).to.eql(200)
        localStorage.setItem('token', response.body.authorization)
        expect(localStorage.getItem('token')).not.null
        cy.log(localStorage.getItem('token'))
    })      
})
Arquivo de configuração

Recurso nativo do Cypress através do arquivo cypress.json. Vide documentação oficial.

Variáveis globais por ambiente

Para modificar suas variáveis globais por ambiente temos uma pasta criada "cypress/environmentsConfig" com dois possíveis ambientes "Prod" e "Qa" representados pelos arquivos exampleProd.json e exampleQa.json respectivamente.

Foi feita a inclusão de um plugin (/cypress/plugins/index.js) através do método "getConfigurationByFile()" onde podemos alterar o ambiente ao executar pela linha de comando incluindo qual ambiente se deseja:

npx cypress run --env configfile=exampleProd
Geração e uso de token

Vide feature "Orquestração de métodos" para entender como o Token é gerado/orquestrado. Para o uso basta incluir o header na Request as Command e incluir o item do localStorage "token":

Cypress.Commands.add('postProdutos', bodyJson =>{
    cy.api({
        method: 'POST',
        url: '/produtos',
        body: bodyJson,
        headers: {  Authorization : localStorage.getItem('token') }})
})
Parametros via Json, QueryString e Path

Path

Exemplo de uso de parâmetro Path com a requisição Delete Produtos:

Delete Produtos ServeRest

Ao mapear a requisição (Request as Command) incluímos o parâmetro junto ao request service (parâmetro url):

Cypress.Commands.add('deleteProdutos', (productId, failStatusCode) =>{
    cy.api({
        method: 'DELETE',
        url: '/produtos/'+productId,
        headers: {  Authorization : localStorage.getItem('token') },
        failOnStatusCode: failStatusCode
    })
})

QueryString

Exemplo de uso de parâmetro QueryString com a requisição Get Produtos:

Get Produtos ServeRest

Ao mapear a requisição (Request as Command) incluímos o parâmetro junto ao request service (parâmetro url) devendo ser informado quais parâmetros concatenados na camada de testes (integration):

Cypress.Commands.add('getProdutos', queryString =>{
    cy.api({
        method: 'GET',
        url: '/produtos?'+ queryString})
})

Este recurso ainda está em pesquisa para ser otimizado.

Json

Exemplo de uso de parâmetro Json com a requisição Post Produtos:

Post Produtos ServeRest

Neste caso temos um json de envio, com os seguintes parâmetros:

{
  "nome": "nome",
  "preco": "1",
  "descricao": "descricao",
  "quantidade": "1"
}

Ao mapear a requisição (Request as Command) incluímos o parâmetro "body" com nossa estrutura de json "jsonBody". Nossos dados virão da camada de testes (integration):

Cypress.Commands.add('postProdutos', jsonBody =>{
    cy.api({
        method: 'POST',
        url: '/produtos',
        body: jsonBody,
        headers: {  Authorization : localStorage.getItem('token') }}) // header de autenticação
})

Camada de teste com o envio dos dados no teste, video a constante "produto" com os dados mockados:

    it('Produtos - Cadastrar Produto',()=>{

        const produto ={
            "nome": faker.random.uuid(),
            "preco": faker.random.number(),
            "descricao": "Mouse bom",
            "quantidade": "5"
          }

        cy.postProdutos(produto)
            .then(response =>{
            expect(response.status).to.equal(201)
            expect(response.body.message).to.equal("Cadastro realizado com sucesso")            
        })
    })
})
Pipeline de teste via Github Actions

Pipeline feito com Github Actions executado em máquina Linux com os processos:

  • Instancia da aplicação ServeRest local via Docker
  • Execução de todos os testes - Task nativa Cypress

Flows executados disponível aqui;

Arquivo yml disponível aqui;

🆕 Novas features


Para novas features crie uma issue ou verifique o board do projeto.

🌟 Contribuições


Para novas contribuições, faça um fork do projeto, realize as alterações e submeta um Pull Request ou crie uma issue para ser avaliada;