Mocking and Stubbing in Cypress unit tests
Published: 2023-08-04 01:16:52

by [Solisoma Madubueze](https://blog.openreplay.com/authors/solisoma-madubueze)

<blockquote><em>Unit testing forms an integral part of software development, ensuring the reliability and correctness of code. `Cypress` (an alternative to the React Testing Library), a popular front-end testing framework, offers powerful capabilities to streamline the testing process. One critical aspect of unit testing is the ability to simulate and control dependencies, such as `API` calls or external libraries. This article will introduce you to stubbing and mocking in `Cypress` unit tests, explore their significance, and show how to effectively utilize them.</em></blockquote>

## Mocking in Cypress unit tests

Mocking involves simulating the behavior of external dependencies in a controlled manner, allowing us to isolate the code under test and focus on specific scenarios. By substituting the actual implementation of a dependency with a mock object, we gain control over the data and responses returned, making our tests more predictable and reliable. Mocking plays a crucial role in unit tests by eliminating the need for real network requests and external services, thus improving test performance and reducing flakiness.

## How to use mocking in Cypress unit tests

In Cypress, mocking can be achieved using the `cy.intercept()` method introduced in `Cypress 7.0`, which intercepts and controls network requests made by the application. By defining routes and their corresponding responses, we can simulate various scenarios. For example, we can mock a successful API response, simulate network errors, or delay responses to test loading states. It allows us to intercept specific `URLs` or match requests based on various criteria, empowering us to create precise mocks for different test cases. `cy.intercept` comes with different syntax. Below are various ways to write or call `cy.intercept.`
```javascript

cy.intercept(url)
cy.intercept(method, url)
cy.intercept(method, url, staticResponse)
cy.intercept(routeMatcher, staticResponse)
cy.intercept(method, url, routeHandler)
cy.intercept(url, routeMatcher, routeHandler)

```
To understand the `cy.intercept` syntax, check [here](https://docs.cypress.io/api/commands/intercept#method-String). In this article, we will use the `cy.intercept(routeMatcher, staticResponse)` syntax.


To demonstrate the use of mocking, consider an example where a React component relies on an API request to retrieve user data. In our `Cypress` test, we can mock the `API` response using `cy.intercept()`, allowing us to control the data returned and verify how the application handles different scenarios. Here's a code example:
```javascript

// defines the URL path to listen to and allows us to modify its behavior.
cy.intercept(
 {
   method: "GET",
   url: "/api/users",
 },
 {
   statusCode: 200,
   fixture: "users.json",
 }, //points to a JSON file where the response data is located
).as("getUserData");

// visits our website
cy.visit("/dashboard");

// waits and listens when the getUserData URL is called
cy.wait("@getUserData");

// runs after the getUserData URL has been called.
cy.get('[data-testid="results"]')
 .should("contain", "Book 1")
 .and("contain", "Book 2");

```
The `cy.intercept` method in the code above defines the `API` call `Cypress` should listen to and allows us to modify its behavior. It takes two arguments: the `routeMatcher,` and the `staticResponse`.
The `routeMatcher` is where we define the `request` information. While the `staticResponse` is where we define the expected response. The fixture key in the `staticResponse` points to the file where our expected response data is located. The location of the data must be in the `/cypress/fixtures/` directory, provided when we activate `Cypress` in our project. The response data is not limited to the `fixtures` key. `Fixtures` is used when the response data is in a json file. We use the' body' key to add our response data inside the `cy.intercept` method. The code looks like this
```javascript

cy.intercept(
 {
   method: "GET",
   url: "/api/users",
 },
 {
   statusCode: 200,
   body: { topic: "mastering", name: "cypress" },
 },
).as("getUserData");

```
The`.as` creates a tag for that interception. As a result, we can retrieve any `API` call made in the test that matches the supplied arguments whenever one is there.

The `cy.visit` method visits our website that we are about to test and mock. `cy.wait('@getUserData')` tells `Cypress` to not perform any testing until the `API` call has occurred. We can also assert our API response using the `.then` method. Let's write some code on how to assert the `API.`
```javascript

// defines the path to listen to and also allows us to modify its behavior.
cy.intercept(
 {
   method: "GET",
   url: "/api/users",
 },
 {
   statusCode: 200,
   fixture: "users.json",
 }, //points to a JSON file where the response data is located
).as("getUserData");

// visits our website
cy.visit("/dashboard");

// waits and listen when the getUserData URL is being called, then asserts its response
cy.wait("@getUserData").then((interception) => {
 // Assertions based on the mocked data
});

// runs after the getUserData URL has been called.
cy.get('[data-testid="results"]')
 .should("contain", "Book 1")
 .and("contain", "Book 2");

```
To understand more on how to assert the `API` response, check [here](https://docs.cypress.io/guides/guides/network-requests#Waiting)

By mocking the `API` response, we ensure consistent data for our test and verify the application's behavior under different conditions.


<div style="background-color:#efefef; border-radius:8px; padding:10px; display:block;">
<hr/>
<h3><em>Session Replay for Developers</em></h3> 
<p><em>Uncover frustrations, understand bugs and fix slowdowns like never before with <strong><a href="https://github.com/openreplay/openreplay" target="_blank">OpenReplay</a></strong> — an open-source session replay suite for developers. It can be <strong>self-hosted</strong> in minutes, giving you complete control over your customer data.</em></p>
<img alt="OpenReplay" style="margin-top:5px; margin-bottom:5px;" width="768" height="400" src="https://raw.githubusercontent.com/openreplay/openreplay/main/static/openreplay-git-hero.svg" class="astro-UXNKDZ4E" loading="lazy" decoding="async">
<p><em>Happy debugging! <a href="https://openreplay.com" target="_blank">Try using OpenReplay today.</a></em><p>
<hr/>
</div>



## Stubbing in Cypress unit tests

Stubbing involves replacing a function or method with a controlled implementation to control its behavior in a unit test. With this technique, we may test error handling by simulating specific circumstances, manipulating return values, or even creating forced errors. Stubbing is very helpful for managing complicated or external dependencies that are difficult to regulate or predict.
In `Cypress`, stubbing can be accomplished using the `cy.stub()` method, which creates a stubbed version of a function or method. With stubs, we can define the desired behavior, return values, or even trigger specific actions when the stubbed function is called. This empowers us to create controlled test scenarios, ensuring that the code under test behaves as expected. `cy.stub` comes with different syntax on how to use it. Below are various ways to write or call `cy.stub.`
```javascript

cy.stub()
cy.stub(object, method)
cy.stub(object, method, replacerFn)

```
For the examples in this article, we will use the second and third syntax, `cy.stub(object, method)` and `cy.stub(object, method, replacerFn)`.

Suppose our application uses a utility function that generates a random number. In our `Cypress` test, we can stub this function to always return a specific value, allowing us to test specific edge cases consistently. Here's a code example:
```javascript

const randomStub = cy.stub(Math, 'random').returns(0.5);

cy.visit('/dashboard');

// Assertions based on the stubbed

```
In this example, we stub the `Math.random()` function to always return 0.5, enabling us to test a specific condition with a predictable outcome. Because `cy.stub()` creates stubs in a sandbox, all newly formed stubs are automatically reset/restored between tests without requiring your explicit reset or restore. Let's consider other examples.

### Replacing an object method with a function
```javascript
let counter = 0;

const obj = {
 isStubbing(ready) {
   if (ready) {
     return true;
   }
   return false;
 },
};

cy.stub(obj, "isStubbing", () => {
 counter += 1;
});

```
In the code above, we replaced a method of an object with a function. Instead of the function returning true or false, it is incrementing the `counter` variable.

If you don't want `cy.stub()` calls to appear in the Command Log, you can chain `a.log(bool)` method. This could be helpful if your stubs are called too frequently. Below is how to write the code.
```javascript

const obj = {
 func() {},
};
const stub = cy.stub(obj, "func").log(false);

```
Stubs can be aliased, just like `.as()` does. This can help you recognize your stubs in error messages and the `Cypress` command log, enabling you to later assert against them using `cy.get ()`.
```javascript

const obj = {
 func() {},
}
const stub = cy.stub(obj, 'func').as('anyArgs')
const withFunc = stub.withArgs('func').as('withFunc')

obj.func()

expect(stub).to.be.called
cy.get('@withFunc').should('be.called') // purposefully failing assertion

```
## Best practices for stubbing and mocking in Cypress unit tests

Let's see some general considerations on testing.

### Advice for effective stubbing

- Identify the critical functions or methods to stub: Focus on functions or methods that significantly impact the code under test or interact with external dependencies.
- Strike a balance between realism and simplicity: Aim to create stubs that mimic the real behavior of the functions or methods while keeping them simple enough for easy maintenance and readability.
- Maintain stubs as the codebase evolves: As the application code changes, ensure that your stubs accurately reflect the updated behavior, avoiding stale or inconsistent stubs.

### Advice for effective mocking

- Understand the boundaries of mocking in unit tests: While mocking can be powerful, it's essential to focus on mocking only the necessary dependencies and avoid excessive mocking, which can lead to brittle tests.
- Focus on key dependencies: Mock the dependencies that significantly impact the code under tests, such as API calls or database interactions, while relying on real implementations for less critical dependencies.
- Document and organize mock setups: Maintain clear documentation and organization of your mock setups to improve test maintainability and make it easier for other developers to understand the test scenarios.

## Conclusion

For `Cypress` unit tests to be successful and ensure solid code quality, learning the techniques of mocking and stubbing is essential. Developers may better control their test environments, mimic different scenarios, and increase the dependability of their apps by understanding the ideas and recommended practices covered in this article. These methods will let developers create thorough and dependable unit tests using `Cypress`, ultimately resulting in more dependable and stable software solutions.