Network Requests with Cypress

TestJS Summit 2021

Cecelia Martinez, Cypress

@ceceliacreates

Network Requests + Cypress

Cypress executes tests against your application inside the browser

@ceceliacreates

Network Requests + Cypress

@ceceliacreates

View network requests as they occur in the Command Log while testing

Network Requests + Cypress

@ceceliacreates

Click any request in the Command Log to output to the console

Network Requests + Cypress

@ceceliacreates

cy.request()

Execute HTTP requests

cy.intercept()

Interact with network requests

Cypress Real World App

@ceceliacreates

cy.request()

How it works

  • Send an HTTP request from your test code
  • Separate from the functionality of your app

@ceceliacreates

cy.request(url)
cy.request(url, body)
cy.request(method, url)
cy.request(method, url, body)
cy.request(options)

Use cases

  • API Testing
  • Retrieve, Create, or Update data for testing
  • Validate endpoint before proceeding with test

@ceceliacreates

context("GET /bankAccounts", function () {
    it("gets a list of bank accounts for user", function () {
      const { id: userId } = ctx.authenticatedUser!;
      cy.request("GET", `${apiBankAccounts}`).then((response) => {
        expect(response.status).to.eq(200);
        expect(response.body.results[0].userId).to.eq(userId);
      });
    });
  });

cy.intercept()

How it works

  • Cypress routes all HTTP requests - including XMLHttpRequest (XHR) and fetch - through its proxy
  • Any request can be observed or stubbed
  • route handler can be programmatic

@ceceliacreates

Network Spying

Declaring a spy with cy.intercept()

  • Can pass a URL, method, or routeMatcher
  • If no method is passed, ANY method types will match
cy.intercept(url)
cy.intercept(method, url)
cy.intercept(routeMatcher)

// with routeMatcher
cy.intercept({
  url: 'http://example.com/search*',
  query: { q: 'expected terms' },
}).as('search')
  • routeMatcher is an object used to match which HTTP requests will be handled
  • All properties are optional
    • auth, headers, hostname, https, method, middleware, path, pathname, port, query, times, url

Aliasing a spy with .as()

Save the intercepted request to use throughout your test code

cy.intercept({
  url: 'http://example.com/search*',
  query: { q: 'expected terms' },
}).as('search')

Waiting for a request with .wait()

After declaring an intercept, use its alias to wait for the request to occur in your test code

cy.intercept('GET', '/users').as('getUsers')

// test code

cy.wait('@getUsers')

// test code continues after request occurs

Dynamic matching

  • Useful for aliasing GraphQL requests
  • (req) gives us access to cy.intercept API commands
  • Leverage req.alias instead of .as() to only alias if matched
cy.intercept("POST", apiGraphQL, (req) => {
     const { body } = req;
     if (body.hasOwnProperty("query") && body.query.includes("listBankAccount")) {
       req.alias = "gqlListBankAccountQuery";
     }
   });

Network Requests

Asserting on network requests

cy.intercept('GET', '/users').as('getUsers')

// We can make assertions on the request and response

cy.wait('@getUsers').its('request.url').should('include', 'users')
  
cy.wait('@getUsers').then((interception) => {
  
  // we can now access the low level interception
  // that contains the request body, response body, status, etc
  expect(interception.request.url).to.include('users')
})

Network Requests

Waiting for multiple requests

cy.intercept('GET', '/users').as('getUsers')

// We can make assertions on the request and response

cy.wait('@getUsers').its('request.body').should('include', 'user1')

// Create new user and wait for the request again
  
cy.wait('@getUsers').its('request.body').should('include', 'user2')

Network Stubbing

Network Stubbing

// stubs response with a fixture
cy.intercept('/users.json', { fixture: 'users.json' })

// stubs response with JSON body
cy.intercept('/projects', {
  body: [{ projectId: '1' }, { projectId: '2' }],
})

// stubs status, headers, and body
cy.intercept('/not-found', {
  statusCode: 404,
  body: '404 Not Found!',
  headers: {
    'x-not-found': 'true',
  },
})

Pass a response body to stub the actual network response

Dynamic Stubbing

cy.intercept('/billing', (req) => {
  // dynamically get billing plan name at request-time
  const planName = getPlanName()
  // this object will automatically be JSON.stringified and sent as the response
  req.reply({ plan: planName })
})
  • The req.reply() function can be used to send a stub response for an intercepted request
  • Passing a string, object, or StaticResponse
  • With a StaticResponse, you can force a network error, delay/throttle the response, send a fixture, and more

Handling multiple requests

cy.intercept('login', {'user': 'username1', 'authenticated': 'true'}).as('login')

// test code to trigger the network request and stubbed response

cy.wait('@login').its('request.body').should('include', 'username1')
// response returns username1

cy.intercept('login', {'user': 'username2', 'authenticated': 'true'}).as('login')

// test code to trigger the network request and stubbed response

cy.wait('@login').its('request.body').should('include', 'username2')
// response now returns username2
  • As of version 7.0, request handlers supplied to cy.intercept() are now matched starting with the most recently defined request interceptor 
  • This allows users to override request handlers by calling cy.intercept() again

Pros and Cons of Network Stubbing

Approach: Real Server Responses

  • By not stubbing your responses, you are writing true end-to-end tests
  • Guarantees the contract between your client and server is working correctly
  • Suggested use: use sparingly, great for the critical paths of your application

Pros

  • ✅ More likely to work in production
  • Test coverage around server endpoints
  • Great for traditional server-side HTML rendering

Cons

  • ❌ Requires seeding data
  • Much slower
  • Harder to test edge cases

Approach: Stub Server Responses

  • Enables you to control every aspect of the response
  • A great way to control the data that is returned to your client
  • Suggested use: Mix and match, typically have one true end-to-end test, and then stub the rest

Pros

  • ✅ Control of response bodies, status, and headers
  • Can force responses to take longer to simulate network delay
  • No code changes to your server or client code
  • Fast, < 20ms response times

Cons

  • ❌ No guarantee your stubbed responses match the actual data
  • No test coverage on some server endpoints
  • Not as useful if for traditional server side HTML rendering

Network Requests with Cypress

TestJS Summit 2021

Cecelia Martinez, Cypress

@ceceliacreates

Network Requests with Cypress

By Cecelia Martinez

Network Requests with Cypress

Whether you’re testing your UI or API, Cypress gives you all the tools needed to work with and manage network requests. This intermediate-level talk demonstrates how to use the cy.request and cy.intercept commands to execute, spy on, and stub network requests while testing your application in the browser. Learn how the commands work as well as use cases for each, including best practices for testing and mocking your network requests.

  • 14