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.
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
If you have the node, on the folder of your project, run:
npm init -y
And after that, install the cypress:
npm install cypress --save-dev
Now, we need to open the cypress, to do it, run:
npx cypress open
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
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
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)
})
})
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')
});
})
})
})
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)
})
})
})
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
})
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
})
})
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')
})
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)
})
})
})
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)
})
})
})
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)
})
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)
})
})
})
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)
})
})
})
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!