16

When testing with cypress.io, is there a good way to assert that no XHR requests were made to a given URL?

I want to assert that a new foo is added when the "Save" button is clicked, but not when the "Cancel" button is clicked.

Something like this:

cy.route('POST', '/foos').as('postFoo');
cy.get('.cancel-button').click();
cy.route('@postFoo').should('not.have.been.called'); // (magic imaginary syntax!)

I've tried setting up the cy.route with an onRequest callback that does an assert.fail, but that's not failing the test when the the URL is called.

For now, I'm catching my (intentional) "no request occurred" errors like this:

cy.on('fail', (err, runnable) => {
  expect(err.message).to.include('No request ever occurred.');
  return false;
});  

cy.wait('@postFoo', { timeout: 0 }).then(xhr => {
  throw new Error('Unexpected API call.');
});

...which seems to be working, but it certainly doesn't feel very "cypress-y".

Brian Schroer
  • 433
  • 1
  • 6
  • 10

3 Answers3

7

You can re-alias your route and change its onRequest behavior to throw; however it's generally worse to assert that things didn't happen because they are non-deterministic. How long should you wait around for the error before moving on? :

cy.route({
  method: 'POST',
  url: '/foos',
  onRequest: () => {
    throw new Error('Whoops')
  }
})
cy.get('.cancel-button').click();
cy.wait(1000)  // give it one second to throw, then move on

Having assertions like this will only add unnecessary time to your tests. This type of test is know as Conditional Testing as mentioned in the Cypress Docs

kuceb
  • 16,573
  • 7
  • 42
  • 56
  • 2
    "... it's generally worse to assert that things didn't happen" - We got around this by watching for the DOM changes that happened after clicking the cancel button with a `cy.get` call instead of `cy.wait` so we knew the actions had executed without calling the route. – Brian Sep 26 '18 at 21:40
  • How did you know how long to wait for your DOM changes after clicking the cancel button? – kuceb Feb 20 '19 at 14:18
  • We had a good happens-before relationship between the call and hiding the modal that our cancel dialog button was in, so we could rely on the request being completed by time the dialog disappears. For that, we just used the default wait time on our `cy.get` call to wait for the modal to be hidden again, then assumed that since no error occurred and the dialog was closed, the request didn't happen. – Brian Feb 21 '19 at 16:05
  • Similar thing here, we're testing whether a component is caching data correctly client-side (to save expensive queries). If the component updates with the expected content but no request has been fired by the time of update then we know it must have gotten the data from the cache. – Samuel Jaeschke Feb 05 '20 at 03:21
0

I think you could compare the data in cy.state('routes') and check the states.

let routes = cy.state('routes');
// check each URL and if it has "/foos" in it, add it to an array to test
// this step is just an example, there are plenty of different ways to approach this
let foosRoutes = [];
for (let route in routes) {
  for (let req in routes[route].requests) {
    let reqUrl = routes[route].requests[req].request.url;
    // test each URL for "/foos" and if it has it, add the URL to the array
    if((/\/foos/).test(reqUrl)) {
      fooRoutes.push(reqUrl);
    }
  }
};
expect(foosRoutes).to.have.property("length", 0);

Or if you dig through the cy.state('routes') a different way, maybe you can filter by the alias of postFoo and then make sure its requests is empty or the count you expect.

Jason Lydon
  • 7,074
  • 1
  • 35
  • 43
0

The assertion:

.should('not.have.been.called')

cannot be used to check if a network request has been made. It is used for cy.spy()

Your hack works, but you can also try:

cy.intercept('POST', '/foos').as('postFoo');
cy.get('.cancel-button').click();
cy.wait('@postFoo').should('not.exist'); // (magic imaginary syntax!)
psychowhiz
  • 131
  • 3