Welcome to this comprehensive Cypress API Testing tutorial! Whether you’re a beginner looking to get started or an experienced tester aiming to enhance your skills, this guide covers everything you need to know about API testing using Cypress. We’ll delve into various authentication methods, handling XML payloads, OAuth 2 authentication, API chaining, running tests in Jenkins, generating reports, and real-world test cases—all crucial components for robust API testing.
Table of Contents
- Introduction to Cypress for API Testing
- Environment Setup
- Understanding Cypress Project Structure
- Sending HTTP Requests
- Creating Request Bodies
- Handling Query Parameters, Headers, Cookies, and Authorization
- Parsing and Validating JSON Responses
- Running and Managing Tests
- Generating Reports
- Real-World Test Cases with Cypress API Testing
- Best Practices and Tips
- Conclusion
Introduction to Cypress for API Testing
Cypress is a powerful end-to-end testing framework primarily known for front-end testing. However, its capabilities extend to API testing, allowing testers to validate backend services efficiently. Cypress provides a straightforward API for sending HTTP requests, making assertions, and handling responses, which simplifies the process of API testing.
Key Features for API Testing:
- Ease of Use: Simple syntax for sending requests and making assertions.
- Automatic Waiting: Cypress automatically waits for commands and assertions before moving on.
- Real-Time Reloads: Test results update in real-time as you make changes.
- Built-In Assertions: Powerful assertions for validating responses.
Environment Setup
Before diving into API testing, ensure you have Cypress installed and configured in your project.
Installation Steps:
- Initialize Your Project:
mkdir cypress-api-testing cd cypress-api-testing npm init -y
- Install Cypress:
npm install cypress --save-dev
- Open Cypress:
npx cypress open
This command opens the Cypress Test Runner and creates the necessary folder structure (
cypress/
directory). - Folder Structure:
cypress/ fixtures/ integration/ plugins/ support/
Understanding Cypress Project Structure
Cypress organizes tests and related files in a structured manner, making it easy to manage and scale your test suites.
fixtures/
: Stores external pieces of static data (e.g., JSON files) that can be used in tests.integration/
: Contains your test specification files.plugins/
: Allows you to extend Cypress’s functionality with plugins.support/
: Contains reusable behaviors and custom commands.
Example Structure:
cypress/
fixtures/
users.json
integration/
api_testing/
basic_auth.cy.js
api_chaining.cy.js
plugins/
index.js
support/
commands.js
index.js
Sending HTTP Requests
Cypress provides the cy.request()
command to send HTTP requests. This command is versatile and supports various HTTP methods.
GET Requests
Example: Fetching User Data
1. Create Spec File:
Path: cypress/integration/api_testing/get_request.cy.js
2. Write the Test:
describe('GET Request', () => {
it('should retrieve user data successfully', () => {
cy.request({
method: 'GET',
url: 'https://api.example.com/users/1',
headers: {
'Accept': 'application/json'
}
}).then((response) => {
expect(response.status).to.eq(200);
expect(response.body).to.have.property('id', 1);
expect(response.body).to.have.property('name');
expect(response.body).to.have.property('email');
});
});
});
3. Run the Test:
npx cypress open
Select the get_request.cy.js
file in the Test Runner to execute the test.
POST Requests
Example: Creating a New User
1. Create Spec File:
Path: cypress/integration/api_testing/post_request.cy.js
2. Write the Test:
describe('POST Request', () => {
it('should create a new user successfully', () => {
const userPayload = {
name: 'John Doe',
email: `johndoe${Math.floor(Math.random() * 1000)}@example.com`,
password: 'SecurePass123'
};
cy.request({
method: 'POST',
url: 'https://api.example.com/users',
body: userPayload,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
}).then((response) => {
expect(response.status).to.eq(201);
expect(response.body).to.have.property('id');
expect(response.body).to.have.property('name', userPayload.name);
expect(response.body).to.have.property('email', userPayload.email);
});
});
});
3. Run the Test:
Execute via Cypress Test Runner to validate user creation.
PUT Requests
Example: Updating User Information
1. Create Spec File:
Path: cypress/integration/api_testing/put_request.cy.js
2. Write the Test:
describe('PUT Request', () => {
let userId;
before(() => {
// Create a user to update
cy.request({
method: 'POST',
url: 'https://api.example.com/users',
body: {
name: 'Jane Doe',
email: `janedoe${Math.floor(Math.random() * 1000)}@example.com`,
password: 'SecurePass123'
},
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
}).then((response) => {
expect(response.status).to.eq(201);
userId = response.body.id;
Cypress.env('userId', userId);
});
});
it('should update user information successfully', () => {
const updatedData = {
name: 'Jane Smith'
};
cy.request({
method: 'PUT',
url: `https://api.example.com/users/${Cypress.env('userId')}`,
body: updatedData,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
}).then((response) => {
expect(response.status).to.eq(200);
expect(response.body).to.have.property('name', updatedData.name);
});
});
});
3. Run the Test:
Execute via Cypress Test Runner to validate user updates.
DELETE Requests
Example: Deleting a User
1. Create Spec File:
Path: cypress/integration/api_testing/delete_request.cy.js
2. Write the Test:
describe('DELETE Request', () => {
let userId;
before(() => {
// Create a user to delete
cy.request({
method: 'POST',
url: 'https://api.example.com/users',
body: {
name: 'Mark Spencer',
email: `markspencer${Math.floor(Math.random() * 1000)}@example.com`,
password: 'SecurePass123'
},
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
}).then((response) => {
expect(response.status).to.eq(201);
userId = response.body.id;
Cypress.env('userId', userId);
});
});
it('should delete the user successfully', () => {
cy.request({
method: 'DELETE',
url: `https://api.example.com/users/${Cypress.env('userId')}`,
headers: {
'Accept': 'application/json'
},
failOnStatusCode: false // To handle 204 No Content
}).then((response) => {
expect(response.status).to.eq(204);
});
});
});
3. Run the Test:
Execute via Cypress Test Runner to validate user deletion.
Creating Request Bodies
When sending POST or PUT requests, crafting the request body correctly is crucial. Cypress allows you to create request bodies in various ways to suit different testing needs.
Hard-Coded JSON Objects
Using hard-coded JSON objects is straightforward and suitable for static data scenarios.
Example: Creating a User with Hard-Coded Data
describe('Hard-Coded JSON Object', () => {
it('should create a user with hard-coded data', () => {
const user = {
name: 'Alice Wonderland',
email: 'alice@example.com',
password: 'WonderPass123'
};
cy.request({
method: 'POST',
url: 'https://api.example.com/users',
body: user,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
}).then((response) => {
expect(response.status).to.eq(201);
expect(response.body).to.include({
name: user.name,
email: user.email
});
});
});
});
Dynamic Data Generation
Generating dynamic data ensures uniqueness and prevents conflicts, especially when creating resources like users or orders.
Example: Creating a User with Dynamic Data
describe('Dynamic Data Generation', () => {
it('should create a user with dynamic data', () => {
const timestamp = Date.now();
const user = {
name: `User${timestamp}`,
email: `user${timestamp}@example.com`,
password: 'DynamicPass123'
};
cy.request({
method: 'POST',
url: 'https://api.example.com/users',
body: user,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
}).then((response) => {
expect(response.status).to.eq(201);
expect(response.body).to.include({
name: user.name,
email: user.email
});
Cypress.env('userId', response.body.id);
});
});
});
Using Fixture Files
Fixtures allow you to manage test data externally, promoting reusability and cleaner test code.
Example: Using Fixture Files to Create a User
1. Create a Fixture File:
Path: cypress/fixtures/user.json
{
"name": "Bob Builder",
"email": "bob.builder@example.com",
"password": "BuilderPass123"
}
2. Write the Test:
describe('Using Fixture Files', () => {
beforeEach(() => {
cy.fixture('user').as('userData');
});
it('should create a user from fixture data', function() {
cy.request({
method: 'POST',
url: 'https://api.example.com/users',
body: this.userData,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
}).then((response) => {
expect(response.status).to.eq(201);
expect(response.body).to.include({
name: this.userData.name,
email: this.userData.email
});
Cypress.env('userId', response.body.id);
});
});
});
3. Run the Test:
Execute via Cypress Test Runner to validate user creation using fixture data.
Handling Query Parameters, Headers, Cookies, and Authorization
Properly managing query parameters, headers, cookies, and authorization is essential for accurate API testing.
Query Parameters
Query parameters allow you to filter or modify the request based on specific criteria.
Example: Fetching Users with Specific Parameters
describe('Handling Query Parameters', () => {
it('should retrieve active users', () => {
cy.request({
method: 'GET',
url: 'https://api.example.com/users',
qs: {
status: 'active',
limit: 10
},
headers: {
'Accept': 'application/json'
}
}).then((response) => {
expect(response.status).to.eq(200);
response.body.data.forEach(user => {
expect(user.status).to.eq('active');
});
});
});
});
Headers and Cookies
Headers and cookies are vital for passing metadata, authentication tokens, and maintaining session states.
Example: Sending Custom Headers and Managing Cookies
describe('Handling Headers and Cookies', () => {
it('should send custom headers and handle cookies', () => {
cy.request({
method: 'GET',
url: 'https://api.example.com/protected',
headers: {
'Authorization': 'Bearer YOUR_TOKEN',
'Custom-Header': 'CustomValue'
},
withCredentials: true // To include cookies
}).then((response) => {
expect(response.status).to.eq(200);
// Assert on cookies if needed
cy.getCookie('session_id').should('exist');
});
});
});
Bearer Token Authentication
Bearer Tokens are commonly used in APIs for authorization, often with OAuth 2.0.
Example: Testing Bearer Token Authentication
describe('Bearer Token Authentication', () => {
const token = 'your_bearer_token_here';
it('should authenticate using bearer token and retrieve resource', () => {
cy.request({
method: 'GET',
url: 'https://api.example.com/protected',
headers: {
'Authorization': `Bearer ${token}`
}
}).then((response) => {
expect(response.status).to.eq(200);
// Further assertions can be added here
});
});
});
API Key Authentication
API Key Authentication involves sending a unique key with each request, usually as a query parameter or in headers.
Example: Testing API Key Authentication
describe('API Key Authentication', () => {
const apiKey = 'your_api_key_here';
it('should authenticate using API key and retrieve resource', () => {
cy.request({
method: 'GET',
url: `https://api.example.com/data?api_key=${apiKey}`,
headers: {
'Accept': 'application/json'
}
}).then((response) => {
expect(response.status).to.eq(200);
// Additional assertions can be added here
});
});
});
OAuth 2 Authentication
OAuth 2 is a robust authentication framework allowing third-party applications to obtain limited access to user accounts.
Understanding OAuth 2 Components:
- Client Application: Sends HTTP requests (GET, POST, DELETE, etc.).
- Resource Server: Hosts the protected resources.
- Authorization Server (Auth Server): Validates tokens and issues access tokens.
OAuth 2 Flow:
- Create a Client Application: Obtain `client_id` and `client_secret`.
- Obtain Authorization Code: Redirect users to the Auth Server to authorize.
- Exchange Authorization Code for Access Token: Send a POST request with `client_id`, `client_secret`, and `code`.
- Access Protected Resources: Use the access token in API requests.
Example: Testing OAuth 2 Authentication with GitHub API
1. Setup:
- Register a New OAuth Application on GitHub:
- Application Name: Cypress Testing
- Homepage URL:
https://yourapp.com
- Authorization Callback URL:
https://yourapp.com/callback
- Obtain `client_id` and `client_secret`.
2. Obtain Authorization Code:
Manually navigate to the authorization URL to obtain the `code`.
https://github.com/login/oauth/authorize?client_id=YOUR_CLIENT_ID&scope=repo
After authorizing, GitHub redirects to the callback URL with a `code` parameter.
3. Exchange Authorization Code for Access Token:
describe('OAuth 2 Authentication', () => {
let accessToken;
it('should obtain access token', () => {
cy.request({
method: 'POST',
url: 'https://github.com/login/oauth/access_token',
qs: {
client_id: 'YOUR_CLIENT_ID',
client_secret: 'YOUR_CLIENT_SECRET',
code: 'AUTHORIZATION_CODE'
},
headers: {
'Accept': 'application/json'
}
}).then((response) => {
expect(response.status).to.eq(200);
accessToken = response.body.access_token;
Cypress.env('accessToken', accessToken);
cy.log(`Access Token: ${accessToken}`);
});
});
it('should access protected resource using access token', () => {
cy.request({
method: 'GET',
url: 'https://api.github.com/user/repos',
headers: {
'Authorization': `Bearer ${Cypress.env('accessToken')}`,
'Accept': 'application/vnd.github.v3+json'
}
}).then((response) => {
expect(response.status).to.eq(200);
// Further assertions can be added here
});
});
});
4. Explanation:
- `accessToken`: Variable to store the obtained access token.
- First `it` Block (`should obtain access token`):
- `cy.request`: Sends a POST request to GitHub’s OAuth endpoint to exchange the authorization code for an access token.
- `qs`: Query string parameters including `client_id`, `client_secret`, and the obtained `code`.
- `headers`: Specifies that the response should be in JSON format.
- `then` Callback:
- `expect(response.status).to.eq(200)`: Asserts successful token exchange.
- `accessToken = response.body.access_token`: Extracts the access token from the response.
- `Cypress.env(‘accessToken’, accessToken)`: Stores the access token for use in subsequent tests.
- `cy.log(…)`: Logs the access token for debugging purposes (ensure not to expose sensitive tokens in logs).
- Second `it` Block (`should access protected resource using access token`):
- `cy.request`: Sends a GET request to GitHub’s API to fetch the authenticated user’s repositories using the access token.
- `headers`:
- `Authorization`: Sends the bearer token for authentication.
- `Accept`: Specifies the version of the GitHub API.
- `then` Callback:
- `expect(response.status).to.eq(200)`: Asserts successful access to the protected resource.
- Further Assertions: Can include verifying the structure and content of the repositories data.
5. Run the Test:
Execute the test via Cypress Test Runner to ensure OAuth 2 Authentication works seamlessly.
Note: OAuth 2 flows often require manual steps (like obtaining the authorization code). For fully automated testing, consider using OAuth 2’s client credentials grant or other flows suitable for automation.
Parsing and Validating JSON Responses
Validating responses ensures that the API behaves as expected. Cypress provides powerful assertions to verify response data.
Simple JSON Parsing
For straightforward JSON responses, accessing properties is direct and simple.
Example: Validating User Data
describe('Simple JSON Parsing', () => {
it('should validate user data', () => {
cy.request('https://api.example.com/users/1').then((response) => {
expect(response.status).to.eq(200);
expect(response.body).to.have.property('id', 1);
expect(response.body).to.have.property('name');
expect(response.body).to.have.property('email');
});
});
});
Explanation:
- `cy.request`: Sends an HTTP GET request to fetch user data with `id` 1.
- `then` Callback:
- `expect(response.status).to.eq(200)`: Asserts that the request was successful.
- `expect(response.body).to.have.property(‘id’, 1)`: Validates that the `id` property in the response is 1.
- `expect(response.body).to.have.property(‘name’)`: Checks that the `name` property exists.
- `expect(response.body).to.have.property(’email’)`: Checks that the `email` property exists.
Complex JSON Parsing with Loops
When dealing with nested or complex JSON structures, loops and advanced assertions become necessary.
Example: Validating Multiple Users in Response
describe('Complex JSON Parsing with Loops', () => {
it('should validate multiple users', () => {
cy.request('https://api.example.com/users').then((response) => {
expect(response.status).to.eq(200);
expect(response.body).to.have.property('data').that.is.an('array');
response.body.data.forEach(user => {
expect(user).to.have.property('id');
expect(user).to.have.property('name');
expect(user).to.have.property('email');
expect(user).to.have.property('status').to.match(/active|inactive/);
});
});
});
});
Explanation:
- `cy.request`: Sends an HTTP GET request to fetch a list of users.
- `then` Callback:
- `expect(response.status).to.eq(200)`: Asserts successful retrieval.
- `expect(response.body).to.have.property(‘data’).that.is.an(‘array’)`: Ensures that the response contains a `data` property which is an array.
- `response.body.data.forEach`: Iterates through each user in the array to perform individual validations:
- `expect(user).to.have.property(‘id’)`: Checks for the existence of the `id` property.
- `expect(user).to.have.property(‘name’)`: Checks for the existence of the `name` property.
- `expect(user).to.have.property(’email’)`: Checks for the existence of the `email` property.
- `expect(user).to.have.property(‘status’).to.match(/active|inactive/)`: Validates that the `status` property matches either `active` or `inactive`.
Running and Managing Tests
Efficiently running and managing your Cypress tests ensures smooth development and continuous integration workflows.
Running Tests Locally
You can run Cypress tests in two modes: Interactive Mode and Headless Mode.
- Interactive Mode:
npx cypress open
Opens the Cypress Test Runner where you can select and run tests interactively.
- Headless Mode:
npx cypress run
Executes all tests in the terminal without opening the GUI, suitable for CI environments.
Explanation:
- Interactive Mode: Ideal for development and debugging. You can watch tests run in real-time and inspect elements.
- Headless Mode: Efficient for automated environments like CI/CD pipelines. Tests run faster without the overhead of the GUI.
Running Tests in Jenkins
Integrating Cypress tests into Jenkins automates the testing process, ensuring tests run with every build.
Steps to Integrate Cypress with Jenkins:
- Install Jenkins:Ensure Jenkins is installed and running on your system or server.
- Install Necessary Plugins:
- NodeJS Plugin: Allows Jenkins to install and manage Node.js versions.
- Cypress Plugin (Optional): For enhanced Cypress integration.
- Configure Jenkins Job:
- Create a New Job:Select “Freestyle project” or “Pipeline” based on your preference.
- Source Code Management:Connect to your repository (e.g., GitHub) where your Cypress project resides.
- Build Environment:Set up Node.js environment using the NodeJS plugin.
- Build Steps:
- Install Dependencies:
npm install
- Run Cypress Tests:
npx cypress run
- Install Dependencies:
- Post-Build Actions:Archive test results or generate reports as needed.
- Set Up Environment Variables:Store sensitive data like API tokens securely using Jenkins credentials and inject them into the build environment.
- Triggering the Job:Configure triggers such as GitHub webhooks, scheduled builds, or manual triggers to execute Cypress tests automatically.
Example Jenkins Pipeline (Declarative):
pipeline {
agent any
environment {
CYPRESS_API_TOKEN = credentials('cypress-api-token') // Jenkins credentials ID
}
stages {
stage('Checkout') {
steps {
git 'https://github.com/your-repo/cypress-api-testing.git'
}
}
stage('Install Dependencies') {
steps {
sh 'npm install'
}
}
stage('Run Cypress Tests') {
steps {
sh 'npx cypress run --env API_TOKEN=$CYPRESS_API_TOKEN'
}
}
stage('Archive Reports') {
steps {
junit 'cypress/results/*.xml' // Assuming you generate JUnit reports
archiveArtifacts artifacts: 'cypress/screenshots/**/*, cypress/videos/**/*', allowEmptyArchive: true
}
}
}
post {
always {
cleanWs()
}
}
}
Explanation:
- Environment Variables: Securely inject API tokens using Jenkins credentials.
- Stages: Checkout code, install dependencies, run tests, and archive reports.
- Post Actions: Clean workspace to ensure fresh builds.
Monitoring Workflow Runs:
After committing the Jenkinsfile
, navigate to the Actions tab in your Jenkins dashboard to monitor workflow runs. You can view logs, download artifacts, and analyze test results directly from the Jenkins interface.
Generating Reports
Generating detailed reports provides insights into your test runs, helping you identify issues and track progress.
Using Mochawesome
Mochawesome is a popular reporter for Cypress that generates comprehensive and visually appealing reports.
Steps to Integrate Mochawesome:
- Install Mochawesome and Related Packages:
npm install mochawesome mochawesome-merge mochawesome-report-generator --save-dev
- Configure Cypress to Use Mochawesome:
Update Cypress Configuration:
// cypress.json { "reporter": "mochawesome", "reporterOptions": { "reportDir": "cypress/reports/mochawesome", "overwrite": false, "html": false, "json": true } }
- Generate and Merge Reports:
Create a Script in
package.json
:"scripts": { "test": "cypress run", "report": "mochawesome-merge cypress/reports/mochawesome/*.json > mochawesome.json && marge mochawesome.json --reportDir cypress/reports" }
- Run Tests and Generate Reports:
npm run test npm run report
This sequence runs Cypress tests and generates a consolidated Mochawesome report in HTML format.
- View the Report:Open
cypress/reports/mochawesome.html
in your browser to view the detailed test report.
Example Output:
- Mochawesome Report: A comprehensive HTML report that includes test summaries, detailed results, screenshots (if any), and other relevant information.
Integrating Reports with CI/CD
Integrating test reports into your CI/CD pipeline enhances visibility and facilitates better decision-making.
Example: Integrating Mochawesome with Jenkins
- Update Jenkins Pipeline to Generate Reports:After running tests, execute the report generation script:
npm run report
- Archive the Report in Jenkins:
- Post-Build Actions:Use the “Publish HTML reports” plugin to display Mochawesome reports directly in Jenkins.
Configure the plugin to point to the generated
mochawesome.html
file.
- Post-Build Actions:Use the “Publish HTML reports” plugin to display Mochawesome reports directly in Jenkins.
- Configure Jenkins to Handle Reports:
- Install the Mochawesome Report plugin or similar for enhanced reporting features.
- Ensure that the report files are archived and accessible post-build.
Example Jenkins Pipeline Stage for Reporting:
stage('Generate Reports') {
steps {
sh 'npm run report'
}
}
stage('Publish Reports') {
steps {
publishHTML(target: [
reportDir: 'cypress/reports',
reportFiles: 'mochawesome.html',
reportName: 'Mochawesome Report',
alwaysLinkToLastBuild: true,
keepAll: true
])
}
}
Explanation:
- Generate Reports: Runs the report generation script.
- Publish Reports: Uses the
publishHTML
step to display the Mochawesome report in Jenkins.
Note:
Ensure that Jenkins has the necessary permissions and configurations to execute the pipeline successfully.
Real-World Test Cases with Cypress API Testing
Applying Cypress API testing to real-world scenarios ensures that your testing suite is practical and valuable. Below are several real-world test cases across different domains to illustrate how Cypress can be leveraged effectively.
E-commerce API Testing
Scenario: Validate the complete user journey—registering a user, adding items to the cart, checking out, and verifying the order.
1. Test Steps:
- Register User: Send a POST request to create a new user.
- Login User: Authenticate the user and obtain a token.
- Add Items to Cart: Send POST requests to add products to the user’s cart.
- Checkout: Send a POST request to process the order.
- Verify Order: Send a GET request to retrieve and verify the order details.
2. Example Test:
describe('E-commerce User Journey', () => {
let token;
let orderId;
it('should register and login a user', () => {
// Register User
cy.request({
method: 'POST',
url: 'https://api.ecommerce.com/register',
body: {
username: `user${Math.floor(Math.random() * 1000)}`,
password: 'SecurePass123',
email: `user${Math.floor(Math.random() * 1000)}@example.com`
}
}).then((response) => {
expect(response.status).to.eq(201);
});
// Login User
cy.request({
method: 'POST',
url: 'https://api.ecommerce.com/login',
body: {
username: `user${Math.floor(Math.random() * 1000)}`,
password: 'SecurePass123'
}
}).then((response) => {
expect(response.status).to.eq(200);
token = response.body.token;
Cypress.env('authToken', token);
});
});
it('should add items to the cart and checkout', () => {
// Add Items to Cart
cy.request({
method: 'POST',
url: 'https://api.ecommerce.com/cart',
headers: {
'Authorization': `Bearer ${Cypress.env('authToken')}`
},
body: {
productId: '123',
quantity: 2
}
}).then((response) => {
expect(response.status).to.eq(200);
});
// Checkout
cy.request({
method: 'POST',
url: 'https://api.ecommerce.com/checkout',
headers: {
'Authorization': `Bearer ${Cypress.env('authToken')}`
},
body: {
paymentMethod: 'credit_card',
address: '123 Main St'
}
}).then((response) => {
expect(response.status).to.eq(200);
orderId = response.body.orderId;
Cypress.env('orderId', orderId);
});
});
it('should verify the order details', () => {
// Verify Order
cy.request({
method: 'GET',
url: `https://api.ecommerce.com/orders/${Cypress.env('orderId')}`,
headers: {
'Authorization': `Bearer ${Cypress.env('authToken')}`
}
}).then((response) => {
expect(response.status).to.eq(200);
expect(response.body.order).to.have.property('status', 'completed');
});
});
});
3. Explanation:
- `register and login a user` Test:
- Register User:
- Sends a POST request to create a new user with a unique username and email.
- Assertion: Ensures that the user is created successfully (`status 201`).
- Login User:
- Sends a POST request to authenticate the newly created user.
- Assertion: Ensures successful authentication (`status 200`).
- Storing Token: Extracts and stores the authentication token for subsequent requests.
- Register User:
- `add items to the cart and checkout` Test:
- Add Items to Cart:
- Sends a POST request to add a product to the user’s cart.
- Headers: Includes the `Authorization` header with the bearer token.
- Assertion: Ensures the product is added successfully (`status 200`).
- Checkout:
- Sends a POST request to process the order.
- Headers: Includes the `Authorization` header with the bearer token.
- Body: Specifies payment method and shipping address.
- Assertion: Ensures the order is processed successfully (`status 200`).
- Storing Order ID: Extracts and stores the `orderId` for verification.
- Add Items to Cart:
- `verify the order details` Test:
- Verify Order:
- Sends a GET request to retrieve the details of the processed order.
- Headers: Includes the `Authorization` header with the bearer token.
- Assertion: Confirms that the order status is `completed` (`status 200`).
- Verify Order:
4. Run the Test:
Execute via Cypress Test Runner to validate the entire user journey in the e-commerce application.
User Management API Testing
Scenario: Test user creation, retrieval, updating, and deletion to ensure user management functionalities work as intended.
1. Test Steps:
- Create User: Send a POST request to create a new user.
- Retrieve User: Send a GET request to retrieve user details.
- Update User: Send a PUT request to update user information.
- Delete User: Send a DELETE request to remove the user.
2. Example Test:
describe('User Management API Testing', () => {
let userId;
it('should create a new user', () => {
cy.request({
method: 'POST',
url: 'https://api.usermanagement.com/users',
body: {
name: `Test User ${Math.floor(Math.random() * 1000)}`,
email: `testuser${Math.floor(Math.random() * 1000)}@example.com`,
password: 'TestPass123'
},
headers: {
'Content-Type': 'application/json'
}
}).then((response) => {
expect(response.status).to.eq(201);
expect(response.body).to.have.property('id');
userId = response.body.id;
Cypress.env('userId', userId);
});
});
it('should retrieve the created user', () => {
cy.request({
method: 'GET',
url: `https://api.usermanagement.com/users/${Cypress.env('userId')}`,
headers: {
'Authorization': `Bearer ${Cypress.env('authToken')}`
}
}).then((response) => {
expect(response.status).to.eq(200);
expect(response.body).to.have.property('name');
expect(response.body).to.have.property('email');
});
});
it('should update the user details', () => {
cy.request({
method: 'PUT',
url: `https://api.usermanagement.com/users/${Cypress.env('userId')}`,
body: {
name: 'Jane Doe'
},
headers: {
'Authorization': `Bearer ${Cypress.env('authToken')}`,
'Content-Type': 'application/json'
}
}).then((response) => {
expect(response.status).to.eq(200);
expect(response.body).to.have.property('name', 'Jane Doe');
});
});
it('should delete the user', () => {
cy.request({
method: 'DELETE',
url: `https://api.usermanagement.com/users/${Cypress.env('userId')}`,
headers: {
'Authorization': `Bearer ${Cypress.env('authToken')}`
},
failOnStatusCode: false
}).then((response) => {
expect(response.status).to.eq(204);
});
});
});
3. Explanation:
- `create a new user` Test:
- POST Request: Sends a request to create a new user with unique `name` and `email`.
- Assertion: Confirms user creation (`status 201`) and the presence of a unique `id`.
- Storing `userId`: Extracts and stores the user’s `id` for subsequent tests.
- `retrieve the created user` Test:
- GET Request: Fetches the details of the created user using the stored `userId`.
- Headers: Includes the `Authorization` header with the bearer token for authentication.
- Assertion: Validates that the response contains the correct `name` and `email`.
- `update the user details` Test:
- PUT Request: Updates the user’s `name` to “Jane Doe”.
- Headers: Includes the `Authorization` header with the bearer token.
- Assertion: Ensures that the user’s name has been updated correctly (`status 200`).
- `delete the user` Test:
- DELETE Request: Removes the user using the stored `userId`.
- Headers: Includes the `Authorization` header with the bearer token.
- `failOnStatusCode: false`: Allows handling of expected status codes like 204 (No Content) without failing the test.
- Assertion: Confirms successful deletion (`status 204`).
4. Run the Test:
Execute via Cypress Test Runner to validate user management functionalities.
Social Media API Testing
Scenario: Validate the creation of posts, commenting, and liking functionalities within a social media platform.
1. Test Steps:
- Create Post: Send a POST request to create a new post.
- Add Comment: Send a POST request to add a comment to the post.
- Like Post: Send a POST request to like the post.
- Verify Post Details: Send a GET request to verify comments and likes.
2. Example Test:
describe('Social Media API Testing', () => {
let postId;
it('should create a new post', () => {
cy.request({
method: 'POST',
url: 'https://api.socialmedia.com/posts',
body: {
title: 'My First Post',
content: 'This is the content of my first post.'
},
headers: {
'Authorization': `Bearer ${Cypress.env('authToken')}`,
'Content-Type': 'application/json'
}
}).then((response) => {
expect(response.status).to.eq(201);
expect(response.body).to.have.property('id');
postId = response.body.id;
Cypress.env('postId', postId);
});
});
it('should add a comment to the post', () => {
cy.request({
method: 'POST',
url: `https://api.socialmedia.com/posts/${Cypress.env('postId')}/comments`,
body: {
comment: 'Great post!'
},
headers: {
'Authorization': `Bearer ${Cypress.env('authToken')}`,
'Content-Type': 'application/json'
}
}).then((response) => {
expect(response.status).to.eq(201);
expect(response.body).to.have.property('comment', 'Great post!');
});
});
it('should like the post', () => {
cy.request({
method: 'POST',
url: `https://api.socialmedia.com/posts/${Cypress.env('postId')}/like`,
headers: {
'Authorization': `Bearer ${Cypress.env('authToken')}`
}
}).then((response) => {
expect(response.status).to.eq(200);
expect(response.body).to.have.property('likes').that.is.a('number');
});
});
it('should verify post details', () => {
cy.request({
method: 'GET',
url: `https://api.socialmedia.com/posts/${Cypress.env('postId')}`,
headers: {
'Authorization': `Bearer ${Cypress.env('authToken')}`
}
}).then((response) => {
expect(response.status).to.eq(200);
expect(response.body).to.have.property('title', 'My First Post');
expect(response.body).to.have.property('comments').that.is.an('array').and.has.length.greaterThan(0);
expect(response.body).to.have.property('likes').that.is.a('number').and.is.greaterThan(0);
});
});
});
3. Explanation:
- `create a new post` Test:
- POST Request: Creates a new post with a title and content.
- Assertion: Confirms post creation (`status 201`) and stores the `postId` for subsequent tests.
- `add a comment to the post` Test:
- POST Request: Adds a comment to the created post using the stored `postId`.
- Headers: Includes the `Authorization` header with the bearer token.
- Assertion: Ensures the comment was added successfully (`status 201`) and matches the sent comment.
- `like the post` Test:
- POST Request: Sends a request to like the post.
- Headers: Includes the `Authorization` header with the bearer token.
- Assertion: Confirms that the post has been liked (`status 200`) and that the `likes` count is a number.
- `verify post details` Test:
- GET Request: Retrieves the details of the post using the stored `postId`.
- Headers: Includes the `Authorization` header with the bearer token.
- Assertion: Validates that the post title is correct, comments exist, and the likes count has increased.
4. Run the Test:
Execute via Cypress Test Runner to validate social media functionalities.
Payment Gateway API Testing
Scenario: Ensure that payment transactions are processed correctly, including handling of successful and failed payments.
1. Test Steps:
- Initiate Payment: Send a POST request to initiate a payment.
- Verify Payment Status: Send a GET request to verify the payment status.
- Handle Failed Payment: Send a POST request with invalid data to simulate a failed payment.
2. Example Test:
describe('Payment Gateway API Testing', () => {
let paymentId;
it('should initiate a payment successfully', () => {
cy.request({
method: 'POST',
url: 'https://api.paymentgateway.com/payments',
body: {
amount: 100,
currency: 'USD',
paymentMethod: 'credit_card',
cardDetails: {
number: '4111111111111111',
expiry: '12/25',
cvv: '123'
}
},
headers: {
'Authorization': `Bearer ${Cypress.env('authToken')}`,
'Content-Type': 'application/json'
}
}).then((response) => {
expect(response.status).to.eq(201);
expect(response.body).to.have.property('paymentId');
paymentId = response.body.paymentId;
Cypress.env('paymentId', paymentId);
});
});
it('should verify the payment status', () => {
cy.request({
method: 'GET',
url: `https://api.paymentgateway.com/payments/${Cypress.env('paymentId')}`,
headers: {
'Authorization': `Bearer ${Cypress.env('authToken')}`
}
}).then((response) => {
expect(response.status).to.eq(200);
expect(response.body).to.have.property('status', 'completed');
});
});
it('should handle failed payment with invalid card details', () => {
cy.request({
method: 'POST',
url: 'https://api.paymentgateway.com/payments',
body: {
amount: 50,
currency: 'USD',
paymentMethod: 'credit_card',
cardDetails: {
number: '1234567890123456', // Invalid card number
expiry: '01/20', // Expired card
cvv: '000' // Invalid CVV
}
},
headers: {
'Authorization': `Bearer ${Cypress.env('authToken')}`,
'Content-Type': 'application/json'
},
failOnStatusCode: false
}).then((response) => {
expect(response.status).to.eq(400);
expect(response.body).to.have.property('error');
});
});
});
3. Explanation:
- `initiate a payment successfully` Test:
- POST Request: Initiates a payment with valid credit card details.
- Headers: Includes the `Authorization` header with the bearer token.
- Body: Specifies the payment amount, currency, payment method, and card details.
- Assertion: Confirms payment initiation (`status 201`) and stores the `paymentId` for verification.
- `verify the payment status` Test:
- GET Request: Retrieves the status of the initiated payment using the stored `paymentId`.
- Headers: Includes the `Authorization` header with the bearer token.
- Assertion: Ensures that the payment status is `completed` (`status 200`).
- `handle failed payment with invalid card details` Test:
- POST Request: Attempts to initiate a payment with invalid card details to simulate a failed transaction.
- Headers: Includes the `Authorization` header with the bearer token.
- `failOnStatusCode: false`: Prevents Cypress from failing the test if a non-2xx status code is returned.
- Assertion: Confirms that the payment failed (`status 400`) and that an `error` message is present in the response.
4. Run the Test:
Execute via Cypress Test Runner to validate payment gateway functionalities.
Health Check API Testing
Scenario: Regularly verify that critical APIs are operational and meet health standards.
1. Test Steps:
- Ping API: Send a simple GET request to check API availability.
- Validate Health Metrics: Send a GET request to retrieve health metrics and validate their values.
2. Example Test:
describe('Health Check API Testing', () => {
it('should respond to ping', () => {
cy.request({
method: 'GET',
url: 'https://api.example.com/health/ping'
}).then((response) => {
expect(response.status).to.eq(200);
expect(response.body).to.have.property('status', 'ok');
});
});
it('should retrieve and validate health metrics', () => {
cy.request({
method: 'GET',
url: 'https://api.example.com/health/metrics',
headers: {
'Authorization': `Bearer ${Cypress.env('authToken')}`
}
}).then((response) => {
expect(response.status).to.eq(200);
expect(response.body).to.have.property('uptime').that.is.a('number').and.is.greaterThan(0);
expect(response.body).to.have.property('responseTime').that.is.a('number').and.is.lessThan(500);
});
});
});
3. Explanation:
- `respond to ping` Test:
- GET Request: Sends a simple request to the `/health/ping` endpoint to check API availability.
- Assertion: Ensures that the API responds with a status of `ok` (`status 200`).
- `retrieve and validate health metrics` Test:
- GET Request: Fetches health metrics from the `/health/metrics` endpoint.
- Headers: Includes the `Authorization` header with the bearer token for authenticated access.
- Assertion:
- `expect(response.status).to.eq(200)`: Confirms successful retrieval of metrics.
- `expect(response.body).to.have.property(‘uptime’).that.is.a(‘number’).and.is.greaterThan(0)`: Validates that the `uptime` metric is a positive number.
- `expect(response.body).to.have.property(‘responseTime’).that.is.a(‘number’).and.is.lessThan(500)`: Ensures that the `responseTime` is within acceptable limits (e.g., less than 500ms).
Best Practices and Tips
Adhering to best practices ensures that your API tests are reliable, maintainable, and efficient.
- Use Environment Variables:
- Purpose: Store sensitive information like tokens, client secrets, and API keys securely.
- Configuration:
// cypress.json { "env": { "apiToken": "YOUR_API_TOKEN", "clientId": "YOUR_CLIENT_ID", "clientSecret": "YOUR_CLIENT_SECRET" } } // Usage in tests const token = Cypress.env('apiToken'); const clientId = Cypress.env('clientId'); const clientSecret = Cypress.env('clientSecret');
- Handle Dynamic Data:
- Purpose: Prevent conflicts and ensure test reliability by using unique data.
- Example:
const uniqueId = Date.now(); const user = { name: `User${uniqueId}`, email: `user${uniqueId}@example.com`, password: 'UniquePass123' };
- Reuse Code with Custom Commands:
- Purpose: DRY (Don’t Repeat Yourself) principle to manage repetitive tasks like authentication.
- Example:
// cypress/support/commands.js Cypress.Commands.add('login', (username, password) => { cy.request({ method: 'POST', url: 'https://api.example.com/login', body: { username, password }, headers: { 'Content-Type': 'application/json' } }).then((response) => { expect(response.status).to.eq(200); Cypress.env('authToken', response.body.token); }); }); // Usage in Tests describe('Authenticated Tests', () => { before(() => { cy.login('testuser', 'TestPass123'); }); it('should access protected resource', () => { cy.request({ method: 'GET', url: 'https://api.example.com/protected', headers: { 'Authorization': `Bearer ${Cypress.env('authToken')}` } }).then((response) => { expect(response.status).to.eq(200); }); }); });
- Assertion Strategies:
- Purpose: Ensure comprehensive validation beyond just status codes.
- Techniques:
- Property Existence: Check for the presence of specific properties.
- Property Values: Validate that properties have expected values.
- Data Types: Ensure properties are of the correct data type.
- Patterns: Use regular expressions to match patterns.
- Example:
expect(response.body).to.have.property('user').that.includes({ id: 1, name: 'John Doe' }); expect(response.body.user.email).to.match(/^\S+@\S+\.\S+$/); expect(response.body.user.age).to.be.a('number').and.to.be.greaterThan(18);
- Error Handling:
- Purpose: Gracefully handle expected error responses without failing the test.
- Technique:
- Use
failOnStatusCode: false
when testing error scenarios.
- Use
- Example:
cy.request({ method: 'POST', url: 'https://api.example.com/login', body: { username: 'invalidUser', password: 'wrongPass' }, failOnStatusCode: false }).then((response) => { expect(response.status).to.eq(401); expect(response.body).to.have.property('error', 'Invalid credentials'); });
- Organize Tests:
- Purpose: Enhance readability and maintainability by logically structuring tests.
- Techniques:
- Use
describe
blocks to group related tests. - Use
it
blocks for individual test cases. - Nest
describe
blocks for hierarchical organization.
- Use
- Example:
describe('User Management API', () => { describe('Create User', () => { it('should create a user successfully', () => { /* ... */ }); }); describe('Retrieve User', () => { it('should retrieve user details', () => { /* ... */ }); }); // Additional test suites... });
- Maintain Idempotency:
- Purpose: Ensure tests can run multiple times without unintended side effects.
- Techniques:
- Clean up created resources after tests.
- Use unique data to prevent conflicts.
- Example:
after(() => { cy.request({ method: 'DELETE', url: `https://api.example.com/users/${Cypress.env('userId')}`, headers: { 'Authorization': `Bearer ${Cypress.env('authToken')}` }, failOnStatusCode: false }); });
- Use Fixtures for Test Data:
- Purpose: Manage and reuse test data efficiently.
- Techniques:
- Store static data in JSON files within the
fixtures/
directory. - Load fixtures using
cy.fixture
and alias them for use in tests.
- Store static data in JSON files within the
- Example:
describe('Using Fixtures', () => { beforeEach(() => { cy.fixture('user').as('userData'); }); it('should create a user from fixture data', function() { cy.request({ method: 'POST', url: 'https://api.example.com/users', body: this.userData, headers: { 'Content-Type': 'application/json' } }).then((response) => { expect(response.status).to.eq(201); expect(response.body).to.include({ name: this.userData.name, email: this.userData.email }); Cypress.env('userId', response.body.id); }); }); });
- Leverage Cypress Hooks:
- Purpose: Set up preconditions and clean up after tests using hooks like
before
,beforeEach
,after
, andafterEach
. - Example:
describe('API Tests with Hooks', () => { before(() => { // Runs once before all tests in the block cy.request('POST', 'https://api.example.com/setup', { /* setup data */ }); }); beforeEach(() => { // Runs before each test in the block cy.request('POST', 'https://api.example.com/authenticate', { /* auth data */ }).then((response) => { Cypress.env('authToken', response.body.token); }); }); after(() => { // Runs once after all tests in the block cy.request('POST', 'https://api.example.com/teardown'); }); it('should perform a test', () => { /* ... */ }); });
- Purpose: Set up preconditions and clean up after tests using hooks like
- Parallelize Tests:
- Purpose: Reduce execution time, especially beneficial for large test suites.
- Techniques:
- Utilize Cypress’s parallelization features in CI environments.
- Distribute tests across multiple machines or containers.
- Example:
npx cypress run --record --key YOUR_RECORD_KEY --parallel
- Consistent Naming Conventions:
- Purpose: Improve readability and maintainability by using clear and consistent names for test files and test cases.
- Techniques:
- Use descriptive names for test files, e.g.,
user_creation.cy.js
. - Structure test case titles to reflect the functionality being tested.
- Use descriptive names for test files, e.g.,
- Example:
describe('User Creation API', () => { it('should create a new user with valid data', () => { /* ... */ }); it('should fail to create a user with invalid data', () => { /* ... */ }); });
- Document Tests:
- Purpose: Enhance understanding and facilitate collaboration by adding comments and documentation within your tests.
- Techniques:
- Comment on complex logic or workflows.
- Provide context for why certain assertions or steps are performed.
- Example:
describe('Complex API Test', () => { it('should perform a series of actions and validate results', () => { // Step 1: Authenticate and obtain token cy.login('testuser', 'TestPass123'); // Step 2: Create a new resource cy.createResource({ /* resource data */ }).then((resource) => { // Step 3: Validate resource creation expect(resource).to.have.property('id'); }); // Additional steps... }); });
Conclusion
Cypress is not just a tool for front-end testing; its robust features make it an excellent choice for API testing as well. This tutorial covered essential aspects of Cypress API Testing, including handling different authentication methods, parsing JSON and XML payloads, implementing OAuth 2, chaining API requests, running tests in Jenkins, generating comprehensive reports, and applying real-world test cases across various domains.
By following the examples and best practices outlined here, you can build a comprehensive API testing suite that ensures your backend services are reliable, secure, and performant. Integrating Cypress API tests into your CI/CD pipelines further enhances your development workflow, enabling continuous quality assurance and rapid feedback loops.