# Mastering mocking and stubbing in Cypress unit tests Unit testing forms an integral part of software development, ensuring the reliability and correctness of code. `Cypress`, 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 is where stubbing and mocking come into play. In this article, we will delve into the world of stubbing and mocking in `Cypress` unit tests, exploring their significance and how to effectively utilize them. ## 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 even 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 for how to use it. 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 more about the syntax of `cy.intercept` check [here](https://docs.cypress.io/api/commands/intercept#method-String) In this article, we will be using the `cy.intercept(routeMatcher, staticResponse)` syntax. To demonstrate the use of mocking, consider an example where a front-end application relies on an `API` call 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 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 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 one is the `routeMatcher` and the other is 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, which is 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. To add our response data inside the `cy.intercept` method we use the `body` key. 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 will be able to retrieve any `API` call made in the test that matches the supplied arguments whenever one is there. `cy.visit` method visit 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 on our`API` response by 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. ## Stubbing in Cypress unit tests: Stubbing involves replacing a function or method with a controlled implementation to control its behavior during testing. 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 be using the second and third syntax which are `cy.stub(object, method)` and `cy.stub(object, method, replacerFn)`. Let's assume our application makes use of 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 `Cypress` command log, and it enables 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: ### Advice for effective stubbing: - Identify the critical functions or methods to stub: Focus on functions or methods that have a significant impact on 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 have a significant impact on 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. Using these methods will let developers create thorough and dependable unit tests using `Cypress`, which will ultimately result in more dependable and stable software solutions.