Cypress Part III: Request Stubbing and Spying

One of Cypress's most compelling features is the ability to stub and spy HTTP requests our web application makes.

13 Dec 2019

•••

This has got to be one of the most compelling features that Cypress have over WebDriver. Being able to stub responses from HTTP requests allow us to write tests that will have consistent results.

However, it is important to know that currently only XMLHttpRequests can be intercepted by Cypress. Details about a temporary workaround for fetch or other network request types could be found here.

Before We Begin

It might be useful for you to download an updated version of our demo application. This modification includes the addition of an HTTP client and moving of our table data into a JSON file. It should be available here.

Request Stubbing

To start stubbing requests, we will need to tell Cypress we intend to by calling cy.server() within our test.

it('loads and displays attendances', () => {
  cy.server()
})

With the server set up, you can now stub specific routes and return specific responses.

it('loads and displays attendances', () => {
  const attendances = [
    {
      id: 'se-1',
      groupName: 'SE 1',
      attendances: [34, 21, 28, 37, 34, 27],
    },
  ]

  cy.server()

  // A simple stubbing
  cy.route('GET', '/api/attendances.json', attendances)

  // A more configurable version of the stubbing above
  // Using this way allows you do add delays to the response
  cy.route({
    method: 'GET',
    url: '/api/attendances.json',
    response: attendances,
  })

  // the rest of the test that triggers the API call
})

Having the attendances mock response inline could get big and could make our test difficult to read. This is especially true when we start testing for responses that are more complex. Cypress has come up with a novel way of solving this by providing the cy.fixture() helper.

You will have noticed that our test application has a fixtures folder. Anything you put in there can be used as responses for your stubbed requests.

Let us create a new file inside e2e/fixtures/ and call it one-cohort-attendances.json and put the snippet you see below:

[
  {
    "id": "se-1",
    "groupName": "SE 1",
    "attendances": [34, 21, 28, 37, 34, 27]
  }
]

Once we have done that, we could now write our new tests!

describe('Dashboard Page', () => {
  it('loads and displays attendances', () => {
    cy.server()
    cy.route(
      'GET',
      '/api/attendances.json',
      'fixture:one-cohort-attendances.json'
    )

    // or a more complex response by calling cy.fixture and
    // assigning it to a Cypress variable called attendances
    cy.fixture('one-cohort-attendances.json').as('attendances')

    cy.route({
      method: 'GET',
      url: '/api/attendances.json',
      response: '@attendances', // we access the variable by prefixing the name with @
    })

    // since the HTTP request happens when we load the Dashboard
    // we will manually visit the page after we have set up the stubbing

    cy.visit('/#/dashboard')

    // we just need to verify that
    cy.get('[data-test=attendance-row-se-1]').should('exist')
  })
})

Now, if we run Cypress and point it to the dashboard.spec.ts, we should see something like the video below!

Request Spying

Building on top of what we have learnt from stubbing we requests, we are also able to assert that a request has occurred.

it('calls the attendances endpoint when dashboard loads', () => {
  cy.server()

  // we store the request spy onto a Cypress variable
  cy.route('GET', '/api/attendances.json').as('attendancesRequest')

  cy.visit('/#/dashboard')

  // by waiting like so, we are making sure that we only wait for as long as we need
  // this also serves as an assertion since this will fail if the request does not happen
  cy.wait('@attendancesRequest')

  cy.get('tbody>tr').should('have.length', 5)
})

Spying requests can be used in conjunction with stubbing.

Conclusion

Request stubbing and spying can be very useful when testing Single Page Applications. Stubbing allows us to control what data should be returned when a request is done, however, our tests could potentially be false positives if the shape of data from the API changes.

Take the snippet below as our stub data:

{
  "id": "person-id-1",
  "name": "John Smith"
}

The response from the actual API was changed to the one below. Since our stub was not updated, the web application could potentially not error on test but will on production.

{
  "id": "person-id-1",
  "firstName": "John",
  "lastName": "Smith"
}

Further Reading

It might be worth reading about Network Requests in Cypress. In there, they discuss some testing strategies that you might find useful.

A more in-depth documentation of cy.route() can be found here and of cy.fixture() here. Let me know if you have any questions.

Similar Posts

Cypress Part I: Getting Started

In this new series, we look at how we could include Cypress as part of our testing strategy for our single page applications.

15 Nov 20195-minute read

Cypress Part II: Configuration

Now that we have added Cypress to our existing SPA, let's have a look at overriding the default configurations.

04 Dec 20192-minute read

Typescript 3.7

The new version of TypeScript has been released and with it comes optional chaining as well as nullish coalescing!

05 Nov 20191-minute read