API Testing with Cypress - Part I
Tags: javascript, tutorial, api, automation
Published: 2024-03-03 17:29:51

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](#about-cypress)
	- [Quick overview about the test types](#quick-overview-about-the-test-types)
- [Setup of the project](#setup-of-the-project)
- [Automating  tests](#automating-tests) 
- [Using commands on cypress](#using-commands-on-cypress)
- [Conclusion](#conclusion) 

## 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5nw9spqk1qdccpg90z5n.gif)

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: 

```bash
node --version 
```

If you have the node, on the folder of your project, run: 
```bash
npm init -y 
```

And after that, install the cypress: 
```bash
npm install cypress --save-dev
```

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

```bash
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](https://dev.to/m4rri4nne/boost-your-experience-with-postman-part-ii-5cde).  
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 : 

```javascript
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](https://docs.cypress.io/api/commands/request)

Knowing this, our first test will look like: 

```javascript
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: 

```javascript
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: 
```javascript
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: 
```javascript
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: 
```javascript
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: 
```javascript
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**: 
```javascript
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: 
```javascript
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:
```javascript
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)
		})
	})
})
```

```javascript
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](https://github.com/m4rri4nne/cypress_api).   
I hope this content will be useful for you. 

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