Mocking and Stubbing with Cypress — Beginner to Advanced

GlobalLogic UK&I
GlobalLogic UK&I
Published in
10 min readJul 16, 2021

What is Cyrpress

You may have heard about Cypress or even worked with it before. After all, it is a popular frontend testing tool due to its great community, documentation and low learning curve. Cypress is designed to make testing anything that runs in a web browser easier and adopts a developer-friendly approach.

I have worked with Cypress for over a year now and have learned many benefits to the tool — along with its flaws. As such, I am slightly biased towards Cypress. I did give other frontend testing tools a go, such as Selenium and TestCafe, but I found Cypress to be so much easier to use in both its syntax and logic used to interact with applications.

In this blog I will be going through different approaches you can use with Cypress to stub out the backend and 3rd party API services. When I talk about stubbing in this context, I am referring to when an API call is made from the frontend application and the process of catching that call to then perform various testing around it. This provides the ability to test parts of the application in isolation. I will also go over my take on how to approach mocking in Cypress.

What is Stubbing?

There are various approaches at your disposal when working with Cypress for stubbing.

First, let’s briefly define what stubbing is. Put simply, stubbing is where you catch a call your application makes and prevent it from reaching its intended endpoint. Effectively you are cutting off parts of your application in order to test components in isolation.

With cypress you are able to easily stub API calls made from your application and provide a response to the call that is made. This enables the ability to perform some edge case tests on the application. For example, how does the application respond when it receives an error from the backend? Is there a popup or event that is expected to be triggered because of this?

If you want more in-depth reading on this, I highly recommend the blogs ‘Mocks Aren’t Stubs’ and ‘TestDouble’ by Martin Fowler.

Beginner friendly approach to stubbing with Cypress

Getting started with stubbing could feel like a daunting task. However, it is surprisingly simple to use. I will go through how to use `cy.intercept()` which is the new command used in Cypress as of version 6.0.0. Before this you could use `cy.server()` and `cy.route()`. It is important to note that use of `cy.route()` has been depreciated as of version 6.0.0.

I will now go through a very basic implementation to stubbing with Cypress. You will probably find that you will need to use this when performing integrations tests for many applications.

The example application I will use to demonstrate the test code on composes of the following features:

- A form with a submit button that performs a POST request to the backend API when clicked,

- A component that will display an error message on error,

- A component that will display a success message on any response other than an error.

The first test will be checking for the error message to display when an error occurs. Here is the base test for getting started:

/*globals Cypress cy*/describe(‘Feedback Form Tests’, () => {  const submitBtn = ‘[data-qa=”submitBtn”]’;  it(‘should send API request and display Error component’, () => {    cy.visit(Cypress.env(‘HOST’));    cy.get(submitBtn).should(‘be.visible’);    cy.get(submitBtn).click();  });});

When this test is run you should see the following result:

We can see that the test runs and passes along with the Error component rendering after an error has been returned.

Currently, our test does not make key assertions on the functionality that has happened in this test. To start to add more value into this test, add the following to the beginning of the test.

cy.intercept(‘POST’, ‘/your-backend-api’).as(‘backendAPI’);

The `cy.intercept` command can take a couple different arguments. Here I have given it a string ‘POST’ as the first argument. This argument is optional and serves to override the default functionality of matching all methods. The second argument is the URL of the request made. This does not need to be the full URL as the cy.intercept command is able to perform a substring match. As such, you can also use regex, as the second argument.

With passing these arguments into cy.intercept, it ensures that only the API call with a ‘POST’ method is intercepted and it’s URL has to contain the string given as a substring. The `.as` after the intercept command creates a tag for that interception. This provides the ability for every time there is an API call that matches the provided arguments, we will then be able to access that call made in the test.

To see this functionality in action, add the following code to the bottom of the test:

cy.wait(‘@backendAPI’).then(xhr => {  expect(xhr.response.statusCode).to.equal(404);});

Here we are telling Cypress to wait in our test for the backend API to be called. Then when an API call has been made that matches the arguments, we can pass the object of data from the call by using `.then`. With this object we can then assert on the response by checking the status code.

The test run should look like the following:

To finish up this test, perform assertions for the text being displayed and checking that ‘Feedback Form’ is no longer being displayed.

cy.get(‘h1’).should(‘contain’, ‘Oops something went wrong!’);cy.get(‘h1’).should(‘not.contain’, ‘Feedback Form’);

Now we will move onto another test. That is how to test the success path or ‘happy path’ of the react app. To do this, we will perform a similar test as the failure path test we just did.

it(‘should display Success component’, () => {     cy.intercept(‘POST’, ‘/your-backend-api’).as(‘backendAPI’);     cy.visit(Cypress.env(‘HOST’));     cy.get(submitBtn).should(‘be.visible’);     cy.get(submitBtn).click();  });

However, we will change the intercept to now return an object in response to being called. This will prevent an error from being thrown in the application as by defult Cypress will return status code of 200 when you provide a stub response object. Even if it is just an empty object!

cy.intercept(‘POST’, ‘/your-backend-api’, {}).as(‘backendAPI’);

Making this change will now show the success component.

Give this a go yourself by cloning this repository: https://github.com/TheTreeofGrace/playground-cypress-dashboard

Also, why not challenge yourself to find a way to provide more value by using a similar mindset above and adding to the test. The mindset I take is to check against what is different or changed between states.

Something to remember when using cy.intercept is that Cypress will set up the intercepts at the start of the test. This means it does not make a difference where you put cy.intercept in your test. Another thing to note is that currently you cannot change the stub response in the same test. Up to date information on this issue can be found in the Cypress documents here: https://docs.cypress.io/api/commands/intercept.html#Comparison-to-cy-route

Cypress Intercept Intermediate Guide

You might have noticed that the first test we wrote for checking the failure scenario made an actual call. By that I mean it used your internet connection and tried to connect to the backend API. This means that for the first test we did not create a stub but instead we used the intercept command to ‘spy’ on the call that was made without affecting the behaviour of the application at all.

Personally, I find a better practice to follow would be to stub this call with a failure body. This is because it will provide assurance that an error will be returned, providing full control over the test environment.

To implement this involves a small refactor of the cy.intercept stub response. All that is needed is to provide a key value pair using `statusCode` in this object with the value being the error code 404.

Example:

cy.intercept(‘POST’, ‘/your-backend-api’, {             statusCode: 404      }).as(‘backendAPI’);

When you run this test, you should see no difference in the test run behaviour, which is as expected with this refactor.

Now that we are fully controlling the response returned to the API call, we can further build onto this by combining the failure and success path tests. This will involve a little bit of javascript coding, but all will be explained as we go.

The solution will be to create a dynamic response body for the stub. This means that the response for the cy.intercept stub will change depending on actions taken in our test.

To do this, we will create a variable for the statusCode number. This variable will need to be able to change throughout our test so should be delared with `let`. I gave the variable a descriptive name of `dynamicStatusCodeStub` and assigned an initial value of 404.

To make dynamic stubbing work for cy.intercept you need to make use of `req.reply` in order to be able to update the response body. Where stub object was being provided, we will now change this to be an anonymous function. This function will need to take in the argument `req`. Then inside of this function we want to call `req.reply` and give it the statusCode object, this time the value will be the variable that was created.

Here is how it should look:

      let dynamicStatusCodeStub = 404;      cy.intercept(‘POST’, ‘/your-backend-api’, (req) => {         req.reply({            statusCode: dynamicStatusCodeStub         });      }).as(‘backendAPI’);

Give your test a run and you should not see any change in the test at this point.

If you’re feeling confident, challenge yourself with updating the dynamicStatusCodeStub variable in your test to combine the success path test. If you become stuck, the answer is on the branch ‘intermediate-answers’ on the GitHub repository:

https://github.com/TheTreeofGrace/cypress-stub-api

You can see this solution to stubbing can open up further edge cases that you can test inside of Cypress. The ability to be able to change the response to an API call is at your beck and call. I have found this useful when working for projects however, it does have some draw backs.

One being that is can become incredibly messy when working with more complex objects. This is often the case for large scale applications. It is also prone to waste when scaled up as you will have to set it up the dynamic stubs for multiple tests and test suites. It would also be difficult to bypass authentication or pre-setup needed for the tests.

Mocking and Stubbing with Storybook and Cypress Advanced Guide

When I am testing a complex application with long user journeys and many dependencies, I prefer to use Storybook with Cypress.

This is particularly useful when your application uses a Content Management System (CMS) such as Contentful. With Storybook you can create stories which are components of your frontend application. This is a way to render small parts of your application in isolation. This also provides the ability to have control over the initial props sent to that component. This makes it easier to pass in mock data into the component. For the mock data, it is best to get this from the live environment in order to match the behaviour of the component in storybook to how it would behave with that data in your live application.

Further to this, it makes dynamically stubbing the API calls more manageable by creating a wrapper component around the isolated component in Storybook, that can then handle complex stubbing logic. In the end you will end up with a ‘fake’ backend system that you have more control over than the live environment.

Generally, I have found that this system has helped tremendously with getting more value from integration tests and a considerable speed increase in test execution.

The benefits of using Cypress with Storybook can be found further detailed in the blog by Matt Lowry: https://ecs.co.uk/resources/how-to-provide-fast-and-reliable-feedback-whilst-working-with-third-parties/

Due to this being an advanced solution, I will not provide a tutorial on how to set this up today. This is mainly because I do not have an advanced application in my arsenal yet in order to demonstrate an amount of the potential that can be leveraged by this solution.

Summary

To summarise: we started at a basic level where a request is made by the application and then ‘intercepted’ the call-in order to make assertions. Along with providing a basic stub to an API call made in order to test the ‘success’ path of the application.

We then went onto a more intermediate approach which involved to use of dynamic stubbing. With this we were able to combine the two basic path checking tests we wrote into one test. This helps to save resources and provide more value to that individual test.

Building on from this, an advanced solution to mocking and stubbing with Storybook was touched upon. With this solution it will make dynamic stubbing in larger applications more manageable and help to take away logic handling from the tests themselves. An added result of this solution is also the ability to cut out repeated user journeys in order to provide more meaningful and faster tests.

More about the author:

Grace Tree is a Delivery Consultant at ECS, specialising in test automation and DevOps. She started her digital transformation career through the ECS Digital Training Academy in 2019 and went on to succeed on multiple projects for BP via ECS. Grace has also received internal recognition from ECS for her technical prowess, being awarded with the Change Markers Award in 2020.

LinkedIn: https://www.linkedin.com/in/treeofgrace/

Twitter: https://twitter.com/TreeofGrace

*****

References:

Cypress: https://www.cypress.io/

Martin Fowler:

- https://martinfowler.com/articles/mocksArentStubs.html

- https://martinfowler.com/bliki/TestDouble.html

Storybook: https://storybook.js.org/

Miragejs: https://miragejs.com/

--

--

GlobalLogic UK&I
GlobalLogic UK&I

GlobalLogic is a leader in digital engineering. We help brands across the globe design and build innovative products, platforms and digital experiences.