Cypress test with multiple interceptors

566 Views Asked by At

I am trying to write cypress tests that check if data is received into my data table from the API.

One test to check the data, another test to check for an error message if there is no data from the API.

To simulate the API's response (one with data and one without) I am using cy.intercept().

I can't seem to get two separate intercepts to work within my single test file.

What is the recommended procedure to test multiple different intercepts in the same cypress test file, or should they be in separate files?

Any help appreciated!

    describe('Display Data Page', () => {

    beforeEach(() => {

        // Fake the backend Http response using the json file in our cypress fixtures folder.
        cy.intercept('GET', 'https://localhost:3000/GetData', {fixture:'my_data.json'}).as('my_data');

        cy.visit('/display-data');
        
    });

    it('Should display a list of my data', () => {

        cy.wait('@my_data');

        cy.get('.dataGrid').should('not.be.empty');
        cy.get('.dataGrid').should("contain", "Blah");
  
    });

    it('Should display a error message when api is offline', () => {

        // Reply with a HTTP 400 bad request to test error message
        cy.intercept('GET', 'https://localhost:3000/GetData', {status: 400}).as('my_error_data');

        cy.wait('@my_error_data');

        cy.get('.errorMessage').should('not.be.empty');
        cy.get('.errorMessage').should("contain", "Error: API is non-responsive!");
  
    });

});
3

There are 3 best solutions below

4
Aladin Spaz On BEST ANSWER

The basic problem is that the intercept my_error_data is being set too late.

Since intercepts are listeners, they need to be set up before they code that triggers the backend call. That trigger is cy.visit(), but in the second test it runs before the intercept is set up.

Move the visit out of the beforeEach() and into the tests to fix it.

it('Should display a list of my data', () => {

  cy.intercept('GET', 'https://localhost:3000/GetData', {fixture:'my_data.json'})
    .as('my_data')

  cy.visit('/display-data')
  cy.wait('@my_data')

  ... 
})

it('Should display a error message when api is offline', () => {

  cy.intercept('GET', 'https://localhost:3000/GetData', {status: 400})
    .as('my_error_data')

  cy.visit('/display-data')
  cy.wait('@my_error_data')

  ...
})

Keeping the beforeEach()

If you really want to keep the beforeEach() structure, make the StaticResponse part of the cy.intercept() dynamic.

You would only need one intercept with that pattern.


let staticResponses = [
  {fixture:'my_data.json'},
  {status: 400}
]

beforeEach(() => {

  const response = staticResponses.shift() // extract 1st response and reduce array

  cy.intercept('GET', 'https://localhost:3000/GetData', response)
    .as('my_data')

  cy.visit('/display-data')
})

it('Should display a list of my data', () => {
  cy.wait('@my_data')
  ...
})

it('Should display a error message when api is offline', () => {
  cy.wait('@my_data')  // same alias
  ...
})

Example test

Here's an example test to verify the principle.

It turns out that Cypress is smart enough to know when the staticResponse is a fixture that should be sent as body, and when the object represents the whole response.

let staticResponses = [
  {fixture:'example.json'},
  {statusCode: 400}             // use statusCode instead of status
]

beforeEach(() => {
  const response = staticResponses.shift() // extract 1st response and reduce array

  cy.intercept('GET', 'https://localhost:3000/GetData', response)
    .as('my_data')

  cy.visit('/display-data')
})

it('Should display a list of my data', () => {
  cy.wait('@my_data')
    .its('response.body')
    .should('have.property', 'email', '[email protected]')
})

it('Should display a error message when api is offline', () => {
  cy.wait('@my_data')
    .its('response')
    .should('have.property', 'statusCode', 400)
})

enter image description here

0
agoff On

You can set Cypress environment variables in the test configuration (the it, context, or describe blocks). We can utilize this to set custom responses in each it, while keeping a shared beforeEach block.

    describe('Display Data Page', () => {

    beforeEach(() => {
        cy.intercept('GET', 'https://localhost:3000/GetData', Cypress.env('getDataResponse')).as('my_data');
        cy.visit('/display-data');
        cy.wait('@my_data');
    });

    it('Should display a list of my data', { env: { getDataResponse: { fixture: 'my_data.json' } } },() => {
        cy.get('.dataGrid').should('not.be.empty');
        cy.get('.dataGrid').should("contain", "Blah");
    });

    it('Should display a error message when api is offline', { env: { getDataResponse: { status: 400 } } }, () => {
        cy.get('.errorMessage').should('not.be.empty');
        cy.get('.errorMessage').should("contain", "Error: API is non-responsive!");
    });
});

It may be duplicative to include the cy.wait() as Cypress should wait for the cy.visit() to complete before continuing, but without seeing your website, I can't say for certain.

Additionally, if you had a it where most tests require one response (say the fixture: 'my_data.json), you could set that as the default (via the nullish coalescing operator and clearing the 'getDataResponse' environment variable).

beforeEach(() => {
  cy.intercept('GET', 'https://localhost:3000/GetData', 
    Cypress.env('getDataResponse') ?? { fixture: 'my_data.json' }
  );
});

afterEach(() => {
  Cypress.env('getDataResponse', null)
});

There are drawbacks to clearing the data in an afterEach, but assuming you reset the values prior to any other actions in the afterEach, you shouldn't encounter most of them.

0
Hipster Dufus On

You don't need Cypress.env() to pass the response, just use a simple function

function setupIntercept(response) {
  cy.intercept('GET', 'https://localhost:3000/GetData', response)
    .as('my_data')
  cy.visit('/display-data')
}

it('Should display a list of my data', () => {

  setupIntercept({fixture:'example.json'})

  cy.wait('@my_data')
    .its('response.body')
    .should('have.property', 'email', '[email protected]')
})

it('Should display a error message when api is offline', () => {

  setupIntercept({statusCode: 400})

  cy.wait('@my_data')
    .its('response')
    .should('have.property', 'statusCode', 400)
})