Deep Dive into Best Practices for API Automation using Cypress
This blog cover
API Automation refers to the process of automatically testing and verifying the functionality of Application Programming Interfaces (APIs) to ensure that they are working as expected.
- How to Automate API’s using Cypress
- How to Automate API’s using API plugin “cypress-plugin-api” by Filip Hric
- Cypress API Automation best practices
What is API Automation
API Automation refers to the process of automatically testing and verifying the functionality of Application Programming Interfaces (APIs) to ensure that they are working as expected.
API Automation typically done using software tools that automate the process of sending requests to the API, analyzing the responses, and comparing them to the expected results. The goal of API automation is to reduce the time and effort required to manually test APIs and to increase the reliability and consistency of the testing process.
What Is a REST API?
REST API stands for Representational State Transfer API, which is a web standards-based architecture for building web services. It is a common way for communicating between client and server over the internet.
REST APIs use HTTP requests to POST (create), PUT (update), GET (read), and DELETE (delete) data. API automation allows for efficient and thorough testing of REST APIs by making multiple API calls and verifying the responses. This can help to identify and resolve bugs and issues early on in the development process
Here is an example to explain REST API:
Suppose you have a website that lists information about books, and you want to allow other applications to retrieve information about books from your website. You can build a REST API for this.
Here’s how it could work:
- Endpoint: A client application would make a request to a specific URL, known as an endpoint, that represents a collection of books. For example: “https://www.example.com/api/books".
- HTTP Methods: The client would use one of the following HTTP methods to make a request:
- GET: To retrieve information about a book or a collection of books.
- POST: To add a new book to the collection.
- PUT: To update information about a book.
- DELETE: To remove a book from the collection.
Response: The server would respond with data in a specific format, such as JSON, that the client can use to retrieve the information about books.
For example, a client could make a GET request to “https://www.example.com/api/books" to retrieve a list of all books in the collection. The server would respond with a JSON payload that includes information about each book, such as its title, author, and ISBN number.
In this example, the REST API provides a way for client applications to interact with the website’s data about books in a standardized, programmatic way.
Cypress For API Automation
Cypress is a JavaScript-based end-to-end testing framework for web applications. It allows you to write tests for your application’s UI as well as its APIs. Here’s an example of how you could write a test for an API using Cypress
Here is a basic example of using Cypress for API automation:
- Install Cypress:
npm install cypress --save-dev
- Create a test files for GET,POST,PUT and DELETE Method under
cypress/e2e/cypress_api_tc.cy.js
Let see the how GET methods work here to fetching the user’s detail
GET Method
it("GET API testing Using Cypress API Plugin", () => {
cy.request("GET", "https://reqres.in/api/users?page=2").should((response) => {
expect(response.status).to.eq(200);
});
});
POST Method
Let see the how POST methods work, here we are creating new user
it("POST API testing Using Cypress API Plugin", () => {
cy.request("POST", "https://reqres.in/api/users", {
name: "Kailash P",
job: "QAAutomationLabs",
}).should((response) => {
expect(response.status).to.eq(201);
});
});
Let see the how PUT methods work, here we are updating user’s detail
PUT Method
it("PUT API testing Using Flip Plugin", () => {
cy.request("PUT", "https://reqres.in/api/users/2", {
name: "QAAutomationLabs",
job: "QA Automation Engg",
}).should((response) => {
expect(response.status).to.eq(200);
});
});
Let see the how DELETE methods work, here we are deleting user’s detail
Delete Method
it("DELETE API testing Using Cypress API Plugin", () => {
cy.request("DELETE", "https://reqres.in/api/users/2").should((response) => {
expect(response.status).to.eq(204);
});
});
In this example, the cy.request()
function is used to send the request to the endpoint. The response from the API is stored in the response
variable and can then be used to write assertions using Chai.js.
Output Of Execute Test Cases
Below the execution report of test cases
API Automation Using Plugin ‘cypress-plugin-api’
Cypress plugin for effective API testing. Imagine Postman but in Cypress. Prints out information about the API call in the Cypress App UI.
Benefit of using the plugin
- In cypress-plugin-api, we have the command
cy.api()
work almost similar tocy.request()
the main difference is that incy.api()
in addition to calling your API, it will print our information about the API call in your Cypress runner. - All of the info can be viewed in a time-travel snapshots
- Simple table for viewing cookies
- JSON data object and array folding
- Color coding of methods in UI view and in the timeline
Good Part is cy.api() function actually uses cy.request in the background, so it’s exactly the same thing plus visual UI
Set-up cypress-plugin-api
Below are the steps to set up the plugin
For more detail please follow the link
Step 1
Install the plugin using npm or yarn. below are the command
npm i cypress-plugin-api
# or
yarn add cypress-plugin-api
Once the API plugin is installed can be seen in package.json
Step 2
Import the plugin into your cypress/support/e2e.js
file:
import ‘cypress-plugin-api’
// or
require(‘cypress-plugin-api’)
e2e.js files look like the attached ones below
Step 3
Create cypress_plugin_api.cy.js with Methods (GET, POST, PUT, DELETE)
For demo purposes, I am taking various API method examples from site https://reqres.in/
GET Request
it("GET API testing Using Cypress API Plugin", () => {
cy.api("GET", "https://reqres.in/api/users?page=2").should((response) => {
expect(response.status).to.eq(200);
});
});
POST Request
it("POST API testing Using Cypress API Plugin", () => {
cy.api("POST", "https://reqres.in/api/users", {
name: "morpheus",
job: "leader",
}).should((response) => {
expect(response.status).to.eq(201);
});
});
PUT Request
it("PUT API testing Using Cypress API Plugin", () => {
cy.api("PUT", "https://reqres.in/api/users/2", {
name: "morpheus",
job: "zion resident",
}).should((response) => {
expect(response.status).to.eq(200);
});
});
DELETE Request
it("DELETE API testing Using Cypress API Plugin", () => {
cy.api("DELETE", "https://reqres.in/api/users/2").should((response) => {
expect(response.status).to.eq(204);
});
});
Step 4
API Test Case Execution Report
In the below screenshot, we can see the data of Body, Response, Headers, and Cookies in Cypress App UI.
Earlier we have to click on inspect to see all this information, but now we have UI to see all this information.
GET Request
POST Request
PUT Request
DELETE Request
Cypress API Automation Best Practices
API Automation Best Practices are guidelines and techniques that help ensure the reliability, maintainability, and efficiency of API tests.
Some of the best practices for API Automation are:
1.Keep tests atomic and independent
When tests are atomic and independent, they are more reliable and less prone to failures caused by the interactions between tests. This makes it easier to isolate and debug failures, as you can run the failing test on its own and not be affected by other tests.
describe('API Test Suite', () => {
beforeEach(() => {
cy.request('GET', '/reset');
});
it('Get User Information', () => {
cy.request('GET', '/users/1')
.its('body')
.should('include', { id: 1, name: 'John Doe' });
});
it('Create a User', () => {
cy.request('POST', '/users', { name: 'Jane Doe' })
.its('body')
.should('include', { id: 2, name: 'Jane Doe' });
});
});
In this example, the beforeEach
block resets the data before each test, which ensures that tests are isolated from each other and do not affect the outcome of other tests.
2.Use fixtures to mock API responses
Fixtures are a way to store external data that can be used in the tests. This allows you to keep the test data separate from the test code, making it easier to maintain and reuse.
Cypress provides the cy.fixture()
command to load data from a fixture file and use it to mock API responses.
// cypress/fixtures/example.json
{
"data": [
{ "id": 1, "name": "John Doe" },
{ "id": 2, "name": "Jane Doe" }
]
}
describe('Example API test', () => {
it('Loads mock data from fixtures', () => {
cy.server();
cy.route('GET', '/api/example', 'fixture:example.json').as('example');
cy.visit('/');
cy.wait('@example')
.its('response.body')
.then((body) => {
expect(body.data[0].id).to.equal(1);
expect(body.data[0].name).to.equal('John Doe');
});
});
});
In this example, the cy.fixture()
method is used to load a JSON file containing user data, and the data is stored as an alias usersJSON
. The response body is then asserted to deep equal the first user in the JSON file.
3.Use Environment variables for configuring API endpoint
You should use environment variables to configure the API endpoint URL and other test data that you use in your test cases. This makes it easier to manage and maintain your test suite, and allows you to change the data without modifying the code.
Store the API endpoint URL in a .env file in the root of your project
API_BASE_URL=https://api.example.com
In your cypress.json file, add the following configuration to make the environment variables accessible in your tests
{
"env": {
"API_BASE_URL": "https://api.example.com"
}
}
Access the API endpoint URL in your tests using Cypress.env()
const apiBaseUrl = Cypress.env('API_BASE_URL');
describe('Books API', () => {
it('Retrieves a list of books', () => {
cy.request(`${apiBaseUrl}/api/books`)
.its('body')
.should('be.an', 'array')
.and('have.length.greaterThan', 0);
});
});
4. Use Yup library schema builder:
Some time the data we received in API response is dynamic so to make assertion on dynamic data not easy. In thats case we can use Yup library
Yup library can be used in API automation to validate the data received from an API response against a defined schema. This ensures that the data received from the API is in the expected format and meets the required constraints.
Here is an example of how to use Yup in Cypress API automation:
import axios from 'axios';
import * as yup from 'yup';
const personSchema = yup.object().shape({
name: yup.string().required(),
age: yup.number().required().positive().integer(),
email: yup.string().email().required(),
});
describe('API test', () => {
it('validates person data received from API', () => {
cy.request('GET', 'https://api.example.com/person')
.then((response) => {
const person = response.body;
try {
personSchema.validateSync(person);
console.log('The data received from the API is valid!');
} catch (err) {
console.error('The data received from the API is invalid: ', err.message);
}
});
});
});
In this example, the test uses the cy.request
command to make a GET request to the API endpoint and retrieve the person data. The response data is then validated against the personSchema
using the validateSync
method. If the data is valid, a message is logged to the console. If the data is invalid, an error message is logged to the console with the validation error.
5. Use Cypress.Commands
Cypress provides a convenient way to define custom commands to be reused across multiple test cases. This can help in reducing the code duplication and making the tests more readable.
Example: Use Cypress.Commands.add
to create a custom command to make API calls and to assert the response.
Cypress.Commands.add('apiCall', (url, method, data) => {
return cy.request({
method: method,
url: url,
body: data
}).then((response) => {
return response;
});
});
// Use the custom command in a test
it('Test API', () => {
cy.apiCall('/api/v1/data', 'GET')
.its('body')
.should('include', { data: 'Sample Data' });
});
6. Cover Positive and Negative Test
Cypress provides a variety of methods for validating API responses, including its(“body”) and its(“status”). These methods allow you to inspect the response from an API request and make assertions about its contents. Here’s an example of validating positive and negative API responses
describe('Test API response', () => {
it('handles positive response', () => {
cy.request('GET', 'https://jsonplaceholder.typicode.com/posts/1')
.its('body')
.should('have.property', 'id', 1)
.should('have.property', 'title', 'sunt aut facere repellat provident occaecati excepturi optio reprehenderit')
});
it('handles negative response', () => {
cy.request({
method: 'GET',
url: 'https://jsonplaceholder.typicode.com/posts/0',
failOnStatusCode: false
}).then((response) => {
expect(response.status).to.eq(404)
expect(response.body).to.be.empty
})
});
});
In the first test, we’re making a GET request to an endpoint that should return a positive response, and we’re checking that the response body has certain properties.
In the second test, we’re making a GET request to an endpoint that should return a 404 status code, indicating a negative response
7. Debugging
Use Cypress debugging features such as the browser dev tools and the cy.debug() command to quickly diagnose and fix issues.
describe('Get user details', () => {
it('should return user details', () => {
cy.request('GET', 'https://jsonplaceholder.typicode.com/users/1')
.its('body')
.then(userDetails => {
cy.log(userDetails);
cy.debug();
})
.should('include', {
id: 1,
name: 'Leanne Graham',
username: 'Bret',
email: 'Sincere@april.biz',
});
});
});
In this example, the cy.request()
command is used to make a GET request to the specified API endpoint. The response is then logged to the console using cy.log()
. Finally, the cy.debug()
command is used to stop the test and open the debugger, allowing you to inspect the response and debug any issues.
8. Use Cy.Intercept()
The cy.intercept()
command in Cypress allows you to intercept and modify network requests made by your application. This can be useful for API automation, as it allows you to control the response that is returned to your application and test how it handles different scenarios.
Here’s an example of using cy.intercept()
in Cypress API automation
describe('Save User', () => {
it('displays a success message when the user is saved', () => {
cy.intercept('/api/save-user', {
method: 'POST',
response: { success: true },
}).as('saveUser');
cy.get('[data-cy=save-button]').click();
cy.wait('@saveUser');
cy.get('[data-cy=success-message]').should('be.visible');
});
it('displays an error message when the user is not saved', () => {
cy.intercept('/api/save-user', {
method: 'POST',
response: { success: false },
}).as('saveUser');
cy.get('[data-cy=save-button]').click();
cy.wait('@saveUser');
cy.get('[data-cy=error-message]').should('be.visible');
});
});
In this example, the cy.intercept()
command is used to intercept the API call to /api/save-user
with a POST method. The response
property is set to an object with a success property, which will be returned to the application when the API call is made. The .as()
method is used to give the intercept a name, which can then be used with cy.wait()
to wait for the intercept to complete before making assertions about the state of the application.
9. Implement error handling to gracefully handle unexpected errors
Your automation test suite should include error handling to gracefully handle unexpected errors. This helps you avoid the test suite crashing or failing when an unexpected error occurs.
Here’s an example of how you can handle unexpected errors in Cypress when making an API call:
cy.request({
method: 'GET',
url: 'https://example.com/api/endpoint'
}).then((response) => {
if (response.status === 500) {
console.error('Unexpected error:', response.body)
} else {
// continue with the test
}
}).catch((err) => {
console.error('Request failed:', err)
})
In this example, if the API returns a status code of 500, it means that an unexpected error has occurred and the error message is logged to the console. If the request fails entirely, the error is caught by the catch block and also logged to the console. You can customize this code to fit your specific error handling needs.
10. Make use of logging to trace errors and debugging information
You should include logging in your automation test suite to trace errors and debugging information. This helps you understand what went wrong when a test fails and makes it easier to debug the problem.
For example, you could log the request and response data for each test case so that you can see what was sent and received during each test.
Here’s an example of how you can log information about an element
cy.get('#element').then((element) => {
console.log('Element text:', element.text())
console.log('Element width:', element.width())
console.log('Element height:', element.height())
})
Here’s another example of how you can log information about a response from an API call
cy.request({
method: 'GET',
url: 'https://example.com/api/endpoint'
}).then((response) => {
console.log('Response status:', response.status)
console.log('Response body:', response.body)
})
Let’s login Into Application Using API (Best Practices)
Suppose you have a test that verifies that a user can log in to your application using the API.
A possible implementation using these best practices would look like this
describe('Login', () => {
beforeEach(() => {
cy.fixture('user.json').as('user');
});
it('should log in successfully', () => {
cy.request({
method: 'POST',
url: `${Cypress.env('apiUrl')}/login`,
body: {
email: this.user.email,
password: this.user.password,
},
}).then((response) => {
expect(response.status).to.eq(200);
expect(response.body).to.have.property('token');
});
});
});
In this example, the fixture user.json
is loaded with cy.fixture()
and stored as this.user
for later use. The Cypress.env()
method is used to retrieve the URL for the API from an environment variable. The test uses cy.request()
to make a POST
request to the /login
endpoint with the user's email and password, and then uses expect()
to verify that the response has a status code of 200 and contains a token property.
Wrap’ Up
Cypress can be used for API automation by making HTTP requests to your application’s APIs and asserting that the responses meet the expected criteria. The Cypress test runner provides a convenient API for making requests and evaluating responses, allowing you to write end-to-end tests that cover both the front-end and back-end of your application.