Mastering Mocking and Stubbing in Cypress: A Comprehensive Guide
Tags: webdev, javascript, testing, cypress
Published: 2024-07-12 07:01:30

## Introduction
When it comes to end-to-end testing, controlling external dependencies can significantly enhance the reliability and speed of your tests. Cypress, a modern web testing framework, offers powerful capabilities for mocking and stubbing HTTP requests, allowing you to simulate different scenarios without relying on actual backend services. In this post, we’ll explore how to leverage Cypress’s `cy.intercept()` for mocking and stubbing API calls to make your tests more robust and efficient.

## Why Mocking and Stubbing?
Mocking and stubbing HTTP requests in Cypress provide several benefits:

1. **Isolation:** Test the frontend independently from the backend, ensuring your tests are not affected by backend changes or outages.
2. **Speed:** Reduce test execution time by avoiding actual network calls.
3. **Reliability:** Create predictable and consistent test conditions by simulating various responses and edge cases.
4. **Flexibility:** Test different scenarios like errors, slow responses, and different data payloads without needing backend changes.

## Setting Up Cypress
If you haven't installed Cypress yet, you can set it up with the following commands:

```bash
npm install cypress --save-dev
npx cypress open
```
Ensure you have the basic Cypress project structure ready before proceeding.

## Using `cy.intercept()`
The `cy.intercept()` command in Cypress allows you to intercept and modify network requests and responses. It replaces the deprecated `cy.route()` command and offers more flexibility and power.

**Basic Example**
Let's start with a basic example where we mock an API response:

```js
// cypress/integration/mock_basic.spec.js
describe('Mocking API Responses', () => {
  it('should display mocked data', () => {
    cy.intercept('GET', '/api/todos', {
      statusCode: 200,
      body: [
        { id: 1, title: 'Mocked Todo 1', completed: false },
        { id: 2, title: 'Mocked Todo 2', completed: true }
      ]
    }).as('getTodos');

    cy.visit('/todos');
    cy.wait('@getTodos');

    cy.get('.todo').should('have.length', 2);
    cy.get('.todo').first().should('contain.text', 'Mocked Todo 1');
  });
});
```
In this example, we intercept a `GET` request to `/api/todos` and provide a mocked response. The `as('getTodos')` assigns an alias to the intercepted request, making it easier to reference in your tests.

## Advanced Mocking Scenarios
**Simulating Errors**
You can simulate various HTTP error statuses to test how your application handles them:

```js
// cypress/integration/mock_errors.spec.js
describe('Simulating API Errors', () => {
  it('should display error message on 500 response', () => {
    cy.intercept('GET', '/api/todos', {
      statusCode: 500,
      body: { error: 'Internal Server Error' }
    }).as('getTodosError');

    cy.visit('/todos');
    cy.wait('@getTodosError');

    cy.get('.error-message').should('contain.text', 'Failed to load todos');
  });
});
```
**Delaying Responses**
To test how your application handles slow network responses, you can introduce a delay:

```js
// cypress/integration/mock_delays.spec.js
describe('Simulating Slow Responses', () => {
  it('should display loading indicator during slow response', () => {
    cy.intercept('GET', '/api/todos', (req) => {
      req.reply((res) => {
        res.delay(2000); // 2-second delay
        res.send({ body: [] });
      });
    }).as('getTodosSlow');

    cy.visit('/todos');
    cy.get('.loading').should('be.visible');
    cy.wait('@getTodosSlow');
    cy.get('.loading').should('not.exist');
  });
});
```

## Mocking Specific Scenarios
**Conditional Mocking**
You can conditionally mock responses based on the request body or headers:

```js
// cypress/integration/mock_conditional.spec.js
describe('Conditional Mocking', () => {
  it('should mock response based on request body', () => {
    cy.intercept('POST', '/api/todos', (req) => {
      if (req.body.title === 'Special Todo') {
        req.reply({
          statusCode: 201,
          body: { id: 999, title: 'Special Todo', completed: false }
        });
      }
    }).as('createTodo');

    cy.visit('/todos');
    cy.get('input[name="title"]').type('Special Todo');
    cy.get('button[type="submit"]').click();

    cy.wait('@createTodo');
    cy.get('.todo').should('contain.text', 'Special Todo');
  });
});
```

## Best Practices for Mocking and Stubbing

1. **Use Aliases:** Always assign aliases to intercepted requests using `.as()`. This makes your tests more readable and easier to debug.
2. **Combine with Fixtures:** Store large mock data in fixture files for better maintainability and readability. Use `cy.fixture()` to load fixtures.
3. **Avoid Over-Mocking:** Only mock what is necessary for the test. Over-mocking can lead to tests that do not reflect real-world scenarios.
4. **Test Real API Calls:** Periodically test against the real backend to ensure your application works correctly with actual data.

## Conclusion
Mocking and stubbing in Cypress are powerful techniques that can make your tests faster, more reliable, and easier to maintain. By intercepting HTTP requests and providing custom responses, you can create a wide range of testing scenarios without relying on external services. Follow the best practices and examples provided in this guide to master mocking and stubbing in your Cypress tests.

Happy testing!