w3resource

Network Requests 2


We will continue from where we stopped in the last tutorial, we will cover how you can use fixtures to reuse XHR responses, how to make use of aliases to refer back to XHR requests and wait on them. Finally, we will show you how to write declarative tests that resist flake.

Routing

'''describe('A sample Route Check', () => {
  it("This will show routes in the Gommand Log", () => {
    cy.server()           // enable response stubbing
    cy.route({
      method: 'GET',      // Route all GET requests
      url: '/www.api.twitter.com/tweets/*',    // that have a URL that matches '/tweets/*'
      response: []        // and force the response to be: []
    })
  })
})'''

Whenever you start a cy.server() and define cy.route() commands, Cypress will display the routes under “Routes” in the Command Log.

cypress: network requests 2 image

Immediately you start a server with cy.server(), all the requests will be controllable for the remainder of the test. When you run a new test, Cypress restores the default behavior and then remove all routing and stubbing.

Fixtures

A fixture refers to a set of data that is located in a file that is used in your tests. The purpose of a test fixture is to ensure that there is a well-known and fixed environment that your tests run in, so that the results are repeatable. You can access fixtures within your Cypress test by calling the cy.fixture() command.

You can stub network requests in Cypress and it will respond instantly with fixture data.

Whenever you stub a response, you will need to manage potentially large and complex JSON objects. Cypress will allow you to integrate fixture syntax directly into responses.

'''cy.server()

// we can then set the response to be the activites.json fixture
cy.route('GET', 'activities/*', 'fixture:activities.json')'''

Additinally you can reference aliases with responses. These aliases don’t have to point to fixtures, although it is the common use case. Separating out a fixture will enable you to work and mutate that object prior to handing it off to a response.

'''cy.server()

cy.fixture('activities.json').as('activitiesJSON')
cy.route('GET', 'activities/*', '@activitiesJSON')'''

Organizing

Cypress will automatically scaffold out a suggested folder structure for organizing your fixtutres on every new project. By default, Cypress will create an example.json file when your project to Cypress.

/cypress/fixtures/example.json

You can further organize your fixtures within additional folders. For example, you can create another folder called images and then add images:

'''/cypress/fixtures/images/cats.png'''
'''/cypress/fixtures/images/dogs.png'''
'''/cypress/fixtures/images/birds.png'''

In order to access the fixtures that are nested within the images folder, you will need to include the folder in your cy.fixture() command.

'''cy.fixture('images/birds.png') //returns birds.png as Base64'''

Waiting

Whether you choose to stub responses or not, Cypress will enable you to declaratively cy.wait() for requests and their responses.

This section will utilize the concept of Aliasing.

An example of aliasing routes and waiting for them subsequently is as shown below:

'''cy.server()
cy.route('activities/*', 'fixture:activities').as('getActivities')
cy.route('messages/*', 'fixture:messages').as('getMessages')

// visit the dashboard, which should 
// make requests that match the two routes above
cy.visit('http://localhost:8888/dashboard')

// pass an array of Route Aliases that forces 
// Cypress to wait until it sees a response for each request 
// that matches each of these aliases
cy.wait(['@getActivities', '@getMessages'])

// these commands will not run until the wait command above resolves
cy.get('h1').should('contain', 'Dashboard')'''

if you want to check the response data for each response of an aliased route, you can make use of several cy.wait() calls.

'''cy.server()
cy.route({
  method: 'POST',
  url: '/myApi',
}).as('apiCheck')

cy.visit('/')
cy.wait('@apiCheck').then((xhr) => {
  assert.isNotNull(xhr.response.body.data, '1st API call has data')
})

cy.wait('@apiCheck').then((xhr) => {
  assert.isNotNull(xhr.response.body.data, '2nd API call has data')
})

cy.wait('@apiCheck').then((xhr) => {
  assert.isNotNull(xhr.response.body.data, '3rd API call has data')
})'''

There are numerous advantages of waiting on an aliased route:

  1. Tests are more robust with much less flake.
  2. Failure messages are much more precise.
  3. You can assert about the underlying XHR object.

Flake

One of the advantages of waiting declaratively for responses is that it will decrease test flakes. You can view cy.wait() as a guard that indicates to Cypress when it should expect a request that matches a specific routing alias to be made. This will prevent the next command from running until the responses come back and guards against situations where your requests are iniatially delayed.

Auto-complete Example:

The fact that cypress will automatically wait for a request that matches the getSearch alias, makes this example very powerful. Rather than forcing Cypress to test the side effect of a successful request (display of the Book results), you can test the actual cause of the results.

'''cy.server()
cy.route('/search*', [{ item: 'Book 1' }, { item: 'Book 2' }]).as('getSearch')

// our autocomplete field will be throttled meaning it
// only makes a request after 500ms from the last keyPress

cy.get('#autocomplete').type('Book')

// wait for the request + response thus 
// insulating us from the throttled request
cy.wait('@getSearch')

cy.get('#results')
  .should('contain', 'Book 1')
  .and('contain', 'Book')'''

Failures

In the example above, an assertion was added to the display of the search results.

The results of the search depend on a few things in the application, these include:

  1. The request is made to the correct URL.
  2. The response is accurately processed by our application.
  3. Our application mounting the result to the DOM.

In our study application, there are many sources of failure. You can easily pinpoint the specific problem in Cypress by adding a cy.wait().

Doing this will enable you know exactly why a test failed.

Assertions

Another benefit of using cy.wait() on requests is that it enables you to access the actual XHR object. This is very useful when you want to make assertions about the XHR object.

In our test example, we can assert about the request object, in order to verify that is sent data as a query string in the URL. Although the response is being mocked, we can also verify that what our application sends is the correct request.

'''cy.server()
// any request to "search/*" endpoint will automatically receive 
// an array with two book objects
cy.route('search/*', [{ item: 'Book 1' }, { item: 'Book 2' }]).as('getSearch')

cy.get('#autocomplete').type('Book')

// this will yield us the XHR object which includes fields for request,
// response, url, method, etc
cy.wait('@getSearch')
  .its('url').should('include', '/search?query=Book')

cy.get('#results')
  .should('contain', 'Book 1')
  .and('contain', 'Book 2')'''

cy.wait() will yield has everything that you need to make assertions including:

  • URL
  • Method
  • Status Code
  • Request Body
  • Request Headers
  • Response Body
  • Response Headers

Here is an example:

'''cy.server()
// this will spy on POST requests to /users endpoint
cy.route('POST', '/users').as('new-user')
// triggers network calls by manipulating web applications?s user interface, then
cy.wait('@new-user')
  .should('have.property', 'status', 201)

// we can grab the completed XHR object again in order to run more assertions
// using cy.get(<alias>)
cy.get('@new-user') // will yield the same XHR object
  .its('requestBody') // alternative: its('request.body')
  .should('deep.equal', {
    id: '101',
    firstName: 'Joe',
    lastName: 'Black'
  })

// then, we can place multiple assertions in a single "should" callback
cy.get('@new-user')
  .should((xhr) => {
    expect(xhr.url).to.match(/\/users$/)
    expect(xhr.method).to.equal('POST')
    // it is a good practice to always add assertion messages as the
    // 2nd argument to expect()
    expect(xhr.response.headers, 'response headers').to.include({
      'cache-control': 'no-cache',
      expires: '-1',
      'content-type': 'application/json; charset=utf-8',
      location: '<domain>/users/101'
    })
  })'''