API Testing with Cypress - Part I

Alicia Marianne - Mar 3 - - Dev Community

Did you know cypress? It's a famous framework for test automation that enables the developers, QAs write faster, easier and more reliable tests. In this tutorial, we will learn what is cypress, how we install it in our project and how to automate API testing.

Summary

About cypress

Cypress is a front end testing tool for modern web. It enables you write faster, easier and more reliable tests.

Cypress execution example

With cypress you can automate different types of tests:

  • E2E
  • Component
  • Integration
  • Unit

The main features of cypress are:

  • Easy debug
  • Cross Browser testing
  • Network traffic control
  • Screenshots, videos and test replay

Quick overview about test types

Lets do a quick overview of the types of tests:

  • End-to-End(E2E): Test the entire application or system as a whole, focus on simulate real user scenarios. The main purpose of this type of test is to validate that all the components, integration are working as expected
  • Integration Tests: Validates the integration between components or systems.
  • Component Tests: Similar to Integration, but focus on testing larger units of code. Verifies the correct behavior of larger sections of the application.
  • Unit Tests: Focuses on testing individual components or functions in isolation.

Setup of the project

Starting this tutorial, let's create our project. The requirements to use cypress is have Node installed with version 18.x or 20.xˆ. To see the version of your node, open the terminal and type:

node --version 
Enter fullscreen mode Exit fullscreen mode

If you have the node, on the folder of your project, run:

npm init -y 
Enter fullscreen mode Exit fullscreen mode

And after that, install the cypress:

npm install cypress --save-dev
Enter fullscreen mode Exit fullscreen mode

Now, we need to open the cypress, to do it, run:

npx cypress open
Enter fullscreen mode Exit fullscreen mode

After opening the launchpad choose e2e testing and accept the configuration. After that, your project will look like:

|--cypress/
|  |-- downloads
|  |-- e2e
|  |-- fixtures
|  |-- support
|      |-- commands.js
|      |-- e2e.js
|-- node_modules
|-- cypress.config.js
|-- package.json
Enter fullscreen mode Exit fullscreen mode

Automating tests

For this tutorial, we will use the same tests cases and API used on my Postman article, you can check here.

Now, lets create our test file. How we are going to automate different methods, we will create one file for each of them. The first will be the GET method, inside of e2e folder, we will create a new file called GETMethod.cy.js:


|-- e2e
|  |-- GETMethod.cy.js
Enter fullscreen mode Exit fullscreen mode

The basic structure of a test of cypress, will be :

describe('Name of the feature', ()=>{
    it('Name of the test itself', ()=>{
        expect(true).to.equal(true)
    })
})
Enter fullscreen mode Exit fullscreen mode

How we are making a GET method, we will change the name of our feature to Testing GET method and create our first test that will be Search all books with success.

To make a request on cypress, we will use the command cy.request() and we need to inform to this command basically this:

  • The method: GET, POST, PUT (if we don't pass, cypress will use the default method - GET)
  • The url
  • The body - if its needed In our test we will inform the method so we can see the difference between the features. You can learn more about this command here

Knowing this, our first test will look like:

describe('Testing GET method', ()=>{
    it('Search all books with success', ()=>{

        cy.request('GET','https://fakerestapi.azurewebsites.net/api/v1/Books').then((response)=>{
            expect(response.status).to.equal(200)
            response.body.forEach(element => {
              expect(element).to.have.property('id')
              expect(element).to.have.property('title')
              expect(element).to.have.property('description')
              expect(element).to.have.property('pageCount')
              expect(element).to.have.property('excerpt')
              expect(element).to.have.property('publishDate')
            });
        })
    })
})
Enter fullscreen mode Exit fullscreen mode

Using the same logic to the other ones, our test Feature will be:

describe('Testing GET method', ()=>{
    it('Search all books with success', ()=>{

        cy.request('GET','https://fakerestapi.azurewebsites.net/api/v1/Books').then((response)=>{
        expect(response.status).to.equal(200)
        response.body.forEach(element => {
          expect(element).to.have.property('id')
          expect(element).to.have.property('title')
          expect(element).to.have.property('description')
          expect(element).to.have.property('pageCount')
          expect(element).to.have.property('excerpt')
          expect(element).to.have.property('publishDate')
        });
        })
    })
     it('Search for a specific books with success', ()=>{
        cy.request('GET','https://fakerestapi.azurewebsites.net/api/v1/Books/10').then((response)=>{
          expect(response.status).to.equal(200)
          expect(response).to.have.property('id')
          expect(response).to.have.property('title')
          expect(response).to.have.property('description')
          expect(response).to.have.property('pageCount')
          expect(response).to.have.property('excerpt')
          expect(response).to.have.property('publishDate')
        })
      })
    it('Search for a specific books with invalid Id', ()=>{
        cy.request('GET','https://fakerestapi.azurewebsites.net/api/v1/Books/10AAA').then((response)=>{
          expect(response.status).to.equal(400)
        })
    }) 
})
Enter fullscreen mode Exit fullscreen mode

Using commands on cypress

Before we start automate the other methods, lets improve our tests. We can make our test easy to read using the commands of cypress. The custom commands on cypress allows you create, and overwrite existing commands. For our tests, we can create two different commands:

  • One that will be responsible to make all our requests - GET, POST, DELETE, PUT
  • One to validate the contract of the API

Let's start with the command that will make all our requests. All commands on cypress will be added on commands.js file, and to add a command, we use the following structure:

Cypress.Commands.add('NameOfTheCommand', (variables)=>{
    //Actions of this command 
})
Enter fullscreen mode Exit fullscreen mode

For our test, we will create the command BaseRequest. We know that for this command, we need to have the url, the method and the body request(for POST and PUT methods), and to allow our bad scenarios, we will pass in this command the parameter failOnStatusCode as true, so it will looks like:

Cypress.Commands.add('BaseRequest', (method, baseUrl, bodyRequest='')=>{
    cy.request({
        'method': method,
        'url': baseUrl,
        'body': bodyRequest == ''? null : bodyRequest,
        'failOnStatusCode': false
    })
})
Enter fullscreen mode Exit fullscreen mode

For the method responsible to validate the contract of the API, we will need to receive on it the response body of the request, so it will be:

Cypress.Commands.add('ContractValidation', (response)=>{
    expect(response).to.have.property('id')
    expect(response).to.have.property('title')
    expect(response).to.have.property('description')
    expect(response).to.have.property('pageCount')
    expect(response).to.have.property('excerpt')
    expect(response).to.have.property('publishDate')
})
Enter fullscreen mode Exit fullscreen mode

Now, we can update our tests:

describe('Testing GET method', ()=>{
    const baseUrl = "https://fakerestapi.azurewebsites.net/api/v1/Books"
    it('Search all books with success', ()=>{
        cy.BaseRequest('GET', baseUrl).then((response)=>{
            expect(response.status).to.equal(200)
            response.body.forEach(element => {
                cy.ContractValidation(element)
            });
        } )
    })
    it('Search for a specific books with success', ()=>{
        const url = baseUrl + "/10"
        cy.BaseRequest('GET', url).then((response)=>{
            expect(response.status).to.equal(200)
            expect(response.body.id).to.equal(10)
            cy.ContractValidation(response.body)
        })
    })
    it('Search for a specific books with invalid Id', ()=>{
        const url = baseUrl + "/10AAA"
        cy.BaseRequest('GET', url).then((response)=>{
            expect(response.status).to.equal(400)
        })
    })
})
Enter fullscreen mode Exit fullscreen mode

Using the commands for the other methods:
DELETE:

describe('Testing DELETE Method', ()=>{
    const baseUrl = "https://fakerestapi.azurewebsites.net/api/v1/Books"
    it('Delete a book with success', ()=>{
        cy.BaseRequest('DELETE', baseUrl + "/100").then((response)=>{
            expect(response.status).to.equal(200)
        })
    })

    it('Update a book passing invalid ID', ()=>{
        cy.BaseRequest('DELETE', baseUrl + "/invalid").then((response)=>{
            expect(response.status).to.equal(400)
        })
    })
})
Enter fullscreen mode Exit fullscreen mode

For the PUT and POST Method, we can create another command that will validate the content of the body response and compare with what was sent:

Cypress.Commands.add('ResponseValidation', (requestBody, responseBody)=>{
    expect(responseBody.id).to.equal(requestBody.id)
    expect(responseBody.title).to.equal(requestBody.title)
    expect(responseBody.description).to.equal(requestBody.description)
    expect(responseBody.pageCount).to.equal(requestBody.pageCount)
    expect(responseBody.excerpt).to.equal(requestBody.excerpt)
    expect(responseBody.publishDate).to.equal(requestBody.publishDate)
})
Enter fullscreen mode Exit fullscreen mode

And our tests will be:

describe('Testing PUT Method', ()=>{
    const baseUrl = "https://fakerestapi.azurewebsites.net/api/v1/Books"
    const body = {
        id: 100,
        title: "string1 updated",
        description: "string1 updated",
        pageCount: 100,
        excerpt: "string",
        publishDate: "2023-10-14T18:44:34.674Z"
    }
    it('Update an book with success', ()=>{
        cy.BaseRequest('PUT', baseUrl + "/100", body).then((response)=>{
            expect(response.status).to.equal(200)
            cy.ContractValidation(response.body)
            cy.ResponseValidation(body, response.body)
            })
    })
    it('Update a book passing invalid ID', ()=>{
        cy.BaseRequest('PUT', baseUrl + "/invalid", body).then((response)=>{
            expect(response.status).to.equal(400)
        })
    })
    it('Update a book with empty body request', ()=>{
        const body = {}
        cy.BaseRequest('PUT', baseUrl + "/100", body).then((response)=>{
            expect(response.status).to.equal(200)
            cy.ContractValidation(response.body)
        })
    })
})
Enter fullscreen mode Exit fullscreen mode
describe('Testing POST Method', ()=>{
    const baseUrl = "https://fakerestapi.azurewebsites.net/api/v1/Books"
    it('Create a book with success', ()=>{
        const body = {
            id: 100,
            title: "string1",
            description: "string1",
            pageCount: 100,
            excerpt: "string",
            publishDate: "2023-10-14T18:44:34.674Z"
        }
        cy.BaseRequest('POST', baseUrl, body).then((response)=>{
            expect(response.status).to.equal(200)
            cy.ContractValidation(response.body)
            cy.ResponseValidation(body, response.body)
        })
    })

    it('Create a book without body request', ()=>{
        cy.BaseRequest('POST', baseUrl).then((response)=>{
            expect(response.status).to.equal(415)
        })
    })

    it('Create a book with empty body request', ()=>{
        const body = {}     
        cy.BaseRequest('POST', baseUrl, body).then((response)=>{
            expect(response.status).to.equal(200)           
            cy.ContractValidaton(response.body)     
        })
    })
})
Enter fullscreen mode Exit fullscreen mode

Conclusion

During this tutorial we learn what is the cypress framework and how we can use it to automate our API Testing. Is important to notice that is only one alternative of how you can automate your API testing using commands, but as we saw, use this practice help us to make our test more simple, easy to maintain and readably.

You can access the project used in this tutorial here

I hope this content will be useful for you. 

If you have any questions, feel free to reach out to me!

. . . . . . . . .