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