A Step-by-Step Guide for comprehensive API testing with Cypress

Cypress is a powerful end-to-end testing framework that provides developers with intuitive and reliable testing tools for modern web applications. While Cypress is widely known for its UI testing capabilities, it also offers robust API testing functionality that can be used to test RESTful APIs, GraphQL endpoints, and more.

Why Choose Cypress for API Testing?

Before we discuss the specifics of testing APIs with Cypress, let’s explore why it’s an excellent choice for API testing.

  1. Full Integration with End-to-End Testing: Cypress allows you to integrate both API and UI tests within a single test suite. This means you can test your APIs in isolation or as part of a complete user journey, verifying that frontend actions trigger the expected backend responses.
  2. Fast Execution: Cypress is designed for speed. Unlike traditional testing tools, Cypress operates directly inside the browser, making API requests almost instantaneous and providing faster test execution.
  3. Interception and Mocking: With cy.intercept(), Cypress can intercept network requests and modify or mock responses, making it ideal for testing edge cases, simulating errors, or testing different response scenarios without modifying the backend.
  4. Powerful Assertion Library: Cypress has a built-in assertion library that supports testing various aspects of an API response, such as status codes, headers, response times, and response bodies. This makes it easier to perform deep validations without relying on additional libraries
  5. Easy to Use: Cypress is known for its user-friendly API and clear documentation. Writing API tests with Cypress is intuitive and doesn’t require a steep learning curve.
  6. Powerful Debugging: Cypress provides powerful debugging capabilities, such as detailed logs and the ability to pause tests. These make identifying and fixing issues with your API tests straightforward.

Let us look at some scenarios for testing API’s with cy.request()

1. Testing a Simple API with cy.request()

The cy.request() command allows you to make HTTP requests (GET, POST, PUT, DELETE, etc.) and validate the responses directly.

Let’s say you want to test a simple RESTful API that returns user information:

Example: GET Request

describe('Api testing with cypress', ()=>{
it('should fetch user details successfully', ()=>{
cy.request('GET', 'https://xyz.com/users/1')
.should((response)=>{
expect(response.status).to.eq(200);
expect(response.body).to.have.property('id', 1);
expect(response.body).to.have.property('name', 'Subhasis Biswal');
});
});
});

In this example:

  1. We use cy.request() to make a GET request to the placeholder API.
  2. We then assert that the response status code is 200, indicating success.
  3. We also verify that the response body contains the expected properties, such as id and name.

2. Testing a POST Request

You test POST requests with cy.request() to check the creation of resources.

Example: POST Request

describe('Api testing with cypress', ()=>{
it('should create a new user successfully', ()=>{
const newUser = {
name: 'Subhasis Biswal',
email: '[email protected]',
phone: '123-456-7890'
}
cy.request('POST', 'https://xyz.com/users', newUser)
.should((response)=>{
expect(response.status).to.eq(201);
expect(response.body).to.have.property('name', 'Subhasis Biswal');
expect(response.body).to.have.property('email', '[email protected]');
});
});

In this case:

  1. We are sending a POST request to create a new user and validate that the response returns a 201 status code, indicating the user was created successfully.
  2. We also check that the returned user details match the data we sent.

3. Validating Response Schema

Sometimes, it is crucial to ensure that the response body adheres to a specific structure or schema. To validate the structure of your API responses, you can use Cypress in combination with third-party libraries like Ajv (Another JSON Schema Validator).

Example: Schema Validation with Ajv

const Ajv = require('ajv');
const ajv = new Ajv();
describe('API Response Schema Validation', () => {
it('Should validate API response schema', () => {
const userSchema = {
type: 'object',
properties: {
id: { type: 'integer'},
name: { type: 'string'},
email: { type: 'string'},
},
required: ['id', 'name', 'email'],
};
cy.request('GET', 'https://api.example.com/user/1').then((response) => {
const validate = ajv.compile(userSchema);
const valid = validate(response.body);
expect(valid, 'Response schema is valid').to.be.true;
});
});
});

In this example, we use the Ajv library to validate that the response body matches the defined JSON schema. This helps ensure that the API response maintains consistency.

Mocking and Stubbing APIs with Cypress:

API testing is integral to modern web development, ensuring that your backend services are functioning as expected. However, real-world APIs may not always be available for testing, or you may want to simulate specific scenarios without relying on an external system. 

This is where mocking and stubbing become essential. In this article, we’ll explore how Cypress, a powerful end-to-end testing framework, can be used for mocking and stubbing APIs in your tests, enabling you to create more reliable and flexible testing scenarios.

1. What is API Mocking and Stubbing?

Before diving into how Cypress handles mocking and stubbing, let’s define these terms:

  1. Mocking: Mocking involves simulating a real API call’s behavior and response, usually for testing purposes. This allows you to control what data or behavior your tests will interact with without needing to rely on external servers or APIs.
  1. Stubbing: Stubbing is a more focused version of mocking, where you replace certain functions or methods (like an API call) with predefined responses. It replaces the real implementation of a function for the purpose of testing.

In the context of Cypress, mocking and stubbing allow you to control the data that your tests will use, simulate various response scenarios (like success or failure), and test how your application reacts to different conditions without needing the actual API.

2. Why Use Mocking and Stubbing in Cypress?

Using Cypress to mock or stub APIs provides several advantages:

  1. Control Over Responses: You can simulate various scenarios, such as error responses, slow network conditions, or edge cases that are difficult to replicate with real APIs.
  1. Faster Tests: Instead of waiting for network requests to go out to real servers, Cypress can intercept the requests and return mock data instantly, speeding up your tests.
  2. No Dependency on External APIs: External APIs may not always be available or reliable. Mocking lets you decouple your tests from external services, ensuring they run consistently.
  3. Test Offline Scenarios: In some cases, you should test how your application handles a lack of connectivity or failures from APIs. Mocking makes this possible.

Now that we understand the importance and benefits of mocking and stubbing let’s see how to implement them with Cypress.

3. Mocking and Stubbing APIs with cy.intercept()

Cypress provides the cy.intercept() command to mock or stub API responses. This command allows you to intercept HTTP requests and modify or replace them with your own predefined responses. cy.intercept() is a versatile tool that can handle both mocking and stubbing.

a. Basic API Mocking with cy.intercept()

Let’s start with a basic example of mocking a simple GET request.

We want to mock the response of an API that retrieves user data.

describe('Mocking API Responses', () => {
it('Should mock user data response', () => {
// Intercept the API request and provide a mocked response
cy.intercept('GET', 'https://xyz.com/users/1', {
statusCode: 200,
body: {
id: 1,
name: 'Mocked User',
email: 'mockeduser@ xyz.com',
},
}).as('getUser');
// Visit a page that triggers the request
cy.visit('/user/1');
// Wait for the intercepted API request
cy.wait('@getUser');
// Verify that the UI reflects the mocked data
cy.get('.user-name').should('contain', 'Mocked User');
cy.get('.user-email').should('contain', 'mockeduser@ xyz.com');
});
});

In this example:

  1. We use cy.intercept() to intercept the GET request made to fetch user data from https://jsonplaceholder.typicode.com/users/1.
  2. We mock the response with a predefined statusCode and body containing mock user data.
  3. After intercepting the request, we use cy.wait() to wait for the request to complete, and then validate that the UI shows the mocked data.

b. Mocking a POST Request

You can also mock POST requests. Let’s say you want to mock a user creation API, which returns a 201 status code (created) with the newly created user’s details.

describe('Mocking POST Request', () => {
it('Should mock user creation response', () => {
const newUser = {
name: 'John Doe',
email: '[email protected]',
};
cy.intercept('POST', 'https://xyz.com/users', {
statusCode: 201,
body: {
id: 11,
name: 'John Doe',
email: '[email protected]',
},
}).as('createUser');
// Simulate form submission to create a new user
cy.get('.name-input').type(newUser.name);
cy.get('.email-input').type(newUser.email);
cy.get('.submit-button').click();
// Wait for the intercepted API request
cy.wait('@createUser');
// Verify the UI reflects the mocked response
cy.get('.user-name').should('contain', 'John Doe');
cy.get('.user-email').should('contain', '[email protected]');
});
});

Here:

  1. We use cy.intercept() to mock a POST request to create a new user.
  2. After triggering the form submission, the test waits for the mocked API request to complete and verifies that the UI displays the correct response, even though no actual API request was made.

c. Simulating Error Responses

Mocking API errors is essential to testing your application’s error handling. With Cypress, you can easily simulate a failed API request.

Example: Mocking a 404 Error

describe('Mocking API Errors', () => {
it('Should handle a 404 error gracefully', () => {
cy.intercept('GET', 'https://xyz.com/users/99', {
statusCode: 404,
body: { message: 'User is not available },
}).as('getUserNotAvailable ');
cy.visit('/user/99');
cy.wait('@getUserNotAvailable');
// Verify that the UI handles the error
cy.get('.error-message').should('contain', 'User is not available');
});
});

In this case:

  1. We mock a 404 error for a non-existent user and return a custom error message.
  1. The test then verifies that the UI correctly displays the error message when the API responds with a 404 status code.

d. Stubbing API Responses with cy.intercept()

While mocking simulates entire API responses, stubbing is more about replacing specific parts of the request. For example, you might stub only the response body while keeping the rest of the request intact.

Example: Stubbing a Response

describe('Stubbing API Responses', () => {
it('Should stub a user data response', () => {
cy.intercept('GET', 'https://xyz.com/users/1', (req) => {
req.reply({
statusCode: 200,
body: {
id: 1,
name: 'Stubbed User',
email: '[email protected]',
},
});
}).as('getUser');
cy.visit('/user/1');
cy.wait('@getUser');
cy.get('.user-name').should('contain', 'Stubbed User');
cy.get('.user-email').should('contain', '[email protected]');
});
});

Here:

  1. We use cy.intercept() to stub the response of the API request.
  1. The req.reply() method allows us to control the response for the intercepted request, simulating the user data without making an actual network call.

e. Delay and Timeout Simulations

Cypress also allows you to simulate delays or network timeouts to test how your application behaves under slow or unstable network conditions.

Example: Simulating a Delay

describe('Simulating Delays', () => {
it('Should handle delayed API response', () => {
cy.intercept('GET', 'https://xyz.com/users/1', (req) => {
req.reply({
delayMs: 2000, // Introduce a 2-second delay
statusCode: 200,
body: {
id: 1,
name: 'Delayed User',
email: '[email protected]',
},
});
}).as('getDelayedUser');
cy.visit('/user/1');
cy.wait('@getDelayedUser');
cy.get('.user-name').should('contain', 'Delayed User');
cy.get('.user-email').should('contain', '[email protected]');
});
});

In this example, we use delayMs to simulate a 2-second delay for the API response, allowing you to test how the UI behaves when the backend is slow.

Mocking and stubbing APIs with Cypress provide a powerful way to simulate various backend scenarios, allowing you to write more robust and flexible tests. Using cy.intercept(), you can intercept requests, modify responses, simulate errors, and even introduce delays to test how your front end handles different situations.

Whether you need to test edge cases, handle unavailable services, or simulate network conditions, Cypress provides a simple yet powerful toolset for mocking and stubbing APIs that integrates seamlessly with your end-to-end tests. This approach not only speeds up your tests but also makes them more reliable by decoupling them from external systems.

Some advanced API testing using Cypress:

1. Handling Authentication in API Testing

In many cases, APIs require authentication, such as using OAuth or Bearer tokens. Cypress makes authentication easy by adding headers to your requests.

Example: Adding Authorization Header

describe('Api testing with Authorization token', ()=>{
it('should fetch protected user token', ()=>{
const bearerToken = 'Bearer your_auth_token_here';
cy.request({
method: 'GET',
url: 'https://xyz.com/users',
headers: {
Authorization: bearerToken,
},
}).should((response)=>{
expect(response.status).to.eq(200);
expect(response.body).to.have.property('data');
});
});
});

In this example, we add the Authorization header to the request, allowing us to access protected resources from the API.

Example a: Testing API with Cookies for Session Management

If your application uses cookies for session management, you can test the API after setting up the cookies in Cypress.

describe('API Testing with Cookies', () => {
it('Should fetch user data with valid session cookie', () => {
cy.setCookie('session_id', 'valid_session_token');
cy.request('GET', 'https://api.example.com/user')
.should((response) => {
expect(response.status).to.eq(200);
expect(response.body).to.have.property('username', 'john_doe');
});
});
});

In this case, cy.setCookie() sets the session cookie, and the request is sent to fetch user data.

Example b: Testing Pagination in API Responses

It’s important to test different pages of results when testing APIs that implement pagination. You can automate this with Cypress to verify that the pagination works correctly.

describe('Testing Pagination in API', () => {
it('Should fetch second page of users', () => {
cy.request('GET', 'https://api.example.com/users?page=2')
.should((response) => {
expect(response.status).to.eq(200);
expect(response.body).to.have.property('page', 2);
expect(response.body.data.length).to.be.greaterThan(0);
});
});
});

This test ensures that the second page of results is fetched correctly and verifies that the pagination functionality returns the correct data for that page.

2. Performance Testing

Although Cypress is primarily used for functional testing, you can also test the performance of API responses by measuring response times. This is especially important to ensure your API performs well under normal usage.

Example: Performance Testing API Response Times

describe('API Performance Testing', () => {
it('Should return response in less than 500ms', () => {
cy.request('GET', 'https://api.example.com/data').should((response) => {
expect(response.duration).to.be.lessThan(500); // Response time should be under 500ms
});
});
});

This test ensures that the API response time is fast enough for your application’s needs.

Best Practices for API Testing with Cypress

  1. Organize Tests: Create separate test files for API testing and UI testing to keep your tests organized.
  2. Use Environment Variables: Store sensitive data such as tokens or API keys in environment variables rather than hard-coding them into your test scripts.
  3. Mock External APIs: Use cy.intercept() to mock external APIs and simulate various response scenarios (e.g., 404, 500, or timeouts) to ensure your app behaves correctly under different conditions.
  4. Test Edge Cases: Make sure to test edge cases, such as invalid inputs, empty responses, and large payloads

Conclusion

API testing with Cypress provides a seamless, efficient way to ensure the integrity of your backend services. With Cypress’ simple yet powerful testing commands, you can quickly validate that your API endpoints are working as expected. 

Whether you’re testing RESTful APIs, simulating error conditions, or validating authorization mechanisms, Cypress gives you the flexibility and reliability needed to build a comprehensive testing strategy.

By integrating API testing into your Cypress test suite, you can ensure that your application’s front end and back-end work together harmoniously, offering a high-quality user experience.


Ready to boost your testing quality?

Join leading software teams who are delivering better products with DEPLOYD. Get started today with a free QA assessment!

Roll out applications to production fast with ease