43

I am trying to assert that a route has not been called in Cypress. I thoroughly looked through the documentation and have found nothing.

I am trying to do something like this:

cy.get('@myRouteAlias').should('have.not.been.called');

I am currently working around this by asserting that the successful request toast message is not being displayed but it is a flimsy solution.

Any ideas?

Chris Bier
  • 14,183
  • 17
  • 67
  • 103

16 Answers16

24

It is very difficult to test a situation where an action has not occured. With this type of assertion, you can really only say:

"The XHR request was not made within the 400ms that Cypress looked for this XHR request to have been made (or whatever you set your timeout to be)"

This doesn't really confirm that the XHR request was never called.

That being said, Cypress offers a way to retrieve all XHR requests made using the undocumented cy.state('requests'). You could check the length of that, filter them by alias, etc to probably determine what you want.

Jennifer Shehane
  • 6,645
  • 1
  • 30
  • 26
  • 10
    In Cypress 6.9.0, it seems that the `state` method is not available anymore. Was it replaced by something else? – luc Apr 06 '20 at 10:39
  • Is there a way to check a particular API is called in the Network Tab, when performing some UI operation? – Ashok kumar Ganesan Jun 18 '21 at 09:40
  • @Jennifer `cy.state(...) is not a function` anymore in Cypress v7 onwards. Is there any other function to do the same? – abdp Jul 01 '21 at 03:32
17

Unfortunately none of the above really worked for me, I got it working with this command :

Cypress.Commands.add('shouldBeCalled', (alias, timesCalled) => {
  expect(
    cy.state('requests').filter(call => call.alias === alias),
    `${alias} should have been called ${timesCalled} times`
  ).to.have.length(timesCalled);
});

Which I then use like this :

// Checks that FetchChatList has not been called
cy.shouldBeCalled('FetchChatList', 0);
guillaumepn
  • 313
  • 3
  • 7
9

None of this worked for me in version 7.6, but I have found a very simple solution.

Given you have an interception like this:

cy.intercept('GET', '**/foo/bar**').as('myRequest');

Now you can just do this:

cy.wait(2000);
cy.get('@myRequest.all').then((interceptions) => {
    expect(interceptions).to.have.length(0);
});

So you wait a certain time, when the request COULD have happened, and make sure after the wait that it didn't. Works perfectly fine for me, and no additional commands are needed. I found that solution here: https://www.gitmemory.com/issue/cypress-io/cypress/15036/780706160

Stiegi
  • 1,074
  • 11
  • 22
8

As a variant set in routes options onResponse function which drops test

e.g. expect(true).to.be.false;

it will fire error if call happened for current route

cy.route({
    url: <url>,
    onResponse: function () {
       expect("Unexpected Https call").to.be.false;
    }
})
pushkin
  • 9,575
  • 15
  • 51
  • 95
  • 1
    This sort of worked. I had to follow the example in the [linked duplicate](https://stackoverflow.com/questions/47295287/cypress-io-assert-no-xhr-requests-to-url) and throw an error instead of using an assertion. Cypress didn't mark the test failed when the assertion occurred. – Brian Sep 26 '18 at 21:38
  • 3
    This isn't working for me, in a strange way. I put a `let putRequestMade = false` outside my tests, and put a log statement and a `putRequestMade = true` inside my route's `onRequest`. I assert on `putRequestMade` before and after the request. When I `expect(putRequestMade).to.eq(true)` after `cy.wait('@putRequest')` that assertion fails, and I can see that the log statement doesn't fire. However, when I remove that `putRequestMade` assertion, I see the log statement, and in debugger I can see that `putRequestMade = true`. Adding a `cy.wait` waits, but w/assertion there, it fails immediately! – Jonathan Tuzman Dec 10 '19 at 16:06
  • Is there a way to check a particular API is called in the Network Tab, when performing some UI operation? – Ashok kumar Ganesan Jun 18 '21 at 09:40
8

Here is the correct way to assert requests count using cypress's commands.

Put this in your commands.js file:

Cypress.Commands.add('requestsCount', (alias) =>
  cy
    .wrap()
    .then(() => cy.state('requests').filter(req => req.alias === alias).length),
);

Than in your tests use a new command as follows:

it('should count requests', () => {
  cy.server();
  cy.route('**').alias('theRequest');

  cy.wait('@theRequest');
  cy.requestsCount('theRequest').should('eq', 1);
});
SleepWalker
  • 1,152
  • 14
  • 17
  • 3
    This abstraction is probably the best among the responses. – k3liutZu Nov 03 '20 at 07:06
  • 1
    For anyone wondering if this works with `cy.intercept`, it doesn't. – Undistraction Oct 28 '21 at 12:20
  • The question is about **has NOT been called** so, I expect `cy.wait("@request")` throw with timeout. Am I wrong? – Hamid Mayeli Aug 05 '23 at 08:49
  • yep. It should throw. If you want to assert that something was called zero times, it's really depends on your case. The problem is that your test async. So if you assert immediately, the counter will be definitely zero but that doesn't mean, that it will be the same after 200ms. So you need to decide what is acceptable for your case and write something like `cy,wait(500); cy.requestsCount('theRequest').should('eq', 0);`. Or probably you should come up with different approach and stop testing for the things that does not exist. – SleepWalker Aug 07 '23 at 11:46
5

It is worth considering the asynchronous nature of this test, something the previous examples have not taken into account. Here is a working example:

cy.route('/my-route').as('myRoute')

const noExpectedCalls = 1

cy.get('@myRoute').then(() => {
  expect(cy.state('requests').filter(r => r.alias === 'myRoute')).to.have.length(noExpectedCalls)
})
connorads
  • 510
  • 1
  • 6
  • 12
Barnaby
  • 861
  • 1
  • 14
  • 21
4

cy.state seems to be undefined when 0.

Also, if you want to call the command with the @, then this will work.

Cypress.Commands.add('shouldBeCalled', (alias, timesCalled) => {
  const aliasname = alias.substring(1);
  const requests = cy.state('requests') || [];

  expect(
    requests.filter((call) => call.alias === aliasname),
    `${aliasname} should have been called ${timesCalled} times`
  ).to.have.length(timesCalled);
});

cy.shouldBeCalled('@updateCalc', 1);
GN.
  • 8,672
  • 10
  • 61
  • 126
3

I tried the simplified version that Jonathan posted, but am seeing TypeError: Cannot read property 'filter' of undefined and cy.state('requests') is always undefined.

3

This is how the cypress team does it (source):

it("throws when alias is never requested", (done) => {
  Cypress.config("requestTimeout", 100);

  cy.on("fail", (err) => {
    expect(err.message).to.include(
      "`cy.wait()` timed out waiting `100ms` for the 1st request to the route: `foo`. No request ever occurred."
    );

    done();
  });

  cy.server().route(/foo/, {}).as("foo").wait("@foo.request");
});

And from the related docs:

Fires when the test has failed. It is technically possible to prevent the test from actually failing by binding to this event and invoking an async done callback. However this is strongly discouraged. Tests should never legitimately fail. This event exists because it’s extremely useful for debugging purposes

Izhaki
  • 23,372
  • 9
  • 69
  • 107
3

Update for cy.intercept() after cy.route() deprecation.

If you are using cy.intercept(), cy.state('requests') will return objects with undefined alias, so I used xhr.url instead.

I adapted the solution of @SleepWalker like this:

Command in commands.js file:

Cypress.Commands.add('requestsCountByUrl', url =>
    cy.wrap().then(() => {
        const requests = cy.state('requests') || [];
        return requests.filter(req => req.xhr.url === url).length;
    })
);

Usage in test:

cy.requestsCountByUrl('http://theUrl.com').should('eq', 1);
2

To simplify @Jennifer Shehane's great answer:

let requestsCount = (alias) => cy.state('requests').filter(a => a.alias === alias).length;

expect(requestsCount('putRequest')).to.eq(0);

And you could put it in your Cypress commands file, too!

Jonathan Tuzman
  • 11,568
  • 18
  • 69
  • 129
1

When we have the route:

cy.intercept('PUT', '**/shoes/*', body).as('updateShoes');

The following solution worked for me:

cy.get('@updateShoes').then((interception) => {
  assert.isNull(interception)
});

Cypress says: expected null to equal null

When the '@updateShoes' route was called than (interception) is a Object:

{id: "interceptedRequest551", routeId: "1623772693273-2831", request: {…}, state: "Complete", requestWaited: false, …}
id: "interceptedRequest551"
log: {get: ƒ, unset: ƒ, invoke: ƒ, toJSON: ƒ, set: ƒ, …}
request: {headers: {…}, url: "http://localhost:8080/api/shoes/38de4e08", method: "PUT", httpVersion: "1.1", body: {…}}
requestWaited: false
response: {headers: {…}, body: {…}, url: "http://localhost:8080/api/shoes/38de4e08", method: null, httpVersion: null, …}
responseWaited: false
routeId: "1623772693273-2831"
state: "Complete"
subscriptions: []
...}

And Cypress throws an error:

AssertionError
expected { Object (id, routeId, ...) } to equal null
Alberto Sinigaglia
  • 12,097
  • 2
  • 20
  • 48
  • You can also shorten the expectation with `cy.get('@updateShoes').should('not.exist')`. However I usually prefer to have speaking errors, thus you can also output a useful error message with `cy.get('@updateShoes').then(interception => expect(interception, 'PUT /api/shoes').to.not.exist)`, which will throw `AssertionError: PUT /api/shoes should not have been called: expected { Object (id, browserRequestId, ...) } to not exist` – Dominik Ehrenberg Sep 26 '22 at 15:13
1

Assertion 'have.not.been.called' is working with .spy() command. It is simple, readable and can be combined with intercept() and wait()

cy.intercept('/my-route', cy.spy().as('myRequest'));

// later in the test

cy.get('@myRequest').should('not.have.been.called'); // not yet intercepted

// something triggers the API call

cy.get('@myRequest').should('have.been.calledOnce'); // now is intercepted

See: https://docs.cypress.io/api/commands/spy

Credits to: https://glebbahmutov.com/blog/cypress-tips-and-tricks/#check-if-the-network-call-has-not-been-made

This answer is taken from here

Jan Tancibok
  • 125
  • 1
  • 11
0

I think I found a way that works for me the way I expected, using cy.intercept and cy.state.

  1. Add your route to be sniffed via cy.intercept
  2. Wait an amount of time, your choice for what you trust
  3. Then see if your URL is in cy.state('routes').
it(`should NOT make foo request`, () => {
  // listen for any request with "foo" using cy.intercept
  // I like to return success just to not see warnings in the console...
  cy.intercept(/.foo./, { success: true }).as("fooRequest");
  cy.window().then(win => {
    // do what ever logic could make the request
    makeFooRequestOrSomething();
  });
  // use cy.wait to wiat whatever amount of time you trust that your logoc should have run
  cy.wait(1000);
  /*
   * cy.intercept does not provide any information unless a request is made, so instead
   * we can use the state and make sure our route is not in the list
   */
  let routes = cy.state('routes'); // An object representing all the routes setup via cy.intercept 
  let fooRoutes = [];
  for (let route in routes) {
    // routes[route].requests is an object representing each request
    for (let req in routes[route].requests) {
      let reqUrl = routes[route].requests[req].request.url;
      // test each URL for "foo" and if it has it, add the URL to the array
      if((/foo/).test(reqUrl)) {
        fooRoutes.push(reqUrl);
      }
    }
  };
  // if no request was made to our URL, our array should be empty
  expect(fooRoutes).to.have.property("length", 0);
});
  • routes[route] probably has the alias somewhere you could use to if you want to filter the data a different way and then see if routes[route].requests is empty.
  • I did not find this documented anywhere, so please let me know if there are better definitions to link to, especially for the cy.state method.
Jason Lydon
  • 7,074
  • 1
  • 35
  • 43
0

As of Cypress 6.0.0, cy.route is replaced by cy.intercept and cy.state is not documented properly.

Thereby, building on the Feodor's answer and the new format,

cy.intercept(<url>, (_) => {
  expect("Unexpected Https call").to.be.false;
})
humble_barnacle
  • 460
  • 1
  • 4
  • 19
-1

Yes, there is a way to assert that a route has not been called. You can use the cy.intercept() command to intercept the request and add a custom handler to check if the request should not be called, and then use the assert.fail() method to explicitly fail the test.

it("should not call the route", () => {
    cy.intercept("/your-route", req => {
        if (shouldNotCallApi) {
            assert.fail("A request was made");
        }
    });
    cy.wait(1000); // before assuming the request was not made
});

It's also a good practice to isolate this test from other tests that might call the same route to prevent interference between them.

Giovanni5454
  • 33
  • 1
  • 6