Is there a way to assert that a route has not been called in Cypress? [duplicate]
Asked Answered
B

17

45

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?

Bazemore answered 17/11, 2017 at 15:45 Comment(2)
duplicate of https://mcmap.net/q/374324/-cypress-io-assert-no-xhr-requests-to-url/927631 ..Sulla
You can now use should("not.have.been.called") in Cypress 13.6 (and possibly earlier, I haven't checked). See github.com/cypress-io/cypress/discussions/… and https://mcmap.net/q/367605/-is-there-a-way-to-assert-that-a-route-has-not-been-called-in-cypress-duplicate below. Note that it's "not.have.been.called", not (as in the question above) "have.not.been.called". You also need to use a spy.Loathing
L
25

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.

Lemke answered 17/11, 2017 at 18:59 Comment(3)
In Cypress 6.9.0, it seems that the state method is not available anymore. Was it replaced by something else?Xeres
Is there a way to check a particular API is called in the Network Tab, when performing some UI operation?Klaipeda
@Jennifer cy.state(...) is not a function anymore in Cypress v7 onwards. Is there any other function to do the same?Investigator
F
18

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);
Ferdinand answered 4/2, 2020 at 12:22 Comment(3)
cy.state is undefined?Impostume
Is there a way to check a particular API is called in the Network Tab, when performing some UI operation?Klaipeda
cy.state('requests') returns undefined in v13 (not in v10), @Stiegi answer works thoughTisza
S
12

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

Selfpropulsion answered 1/7, 2021 at 10:45 Comment(6)
This no longer works.Yezd
This works for me. (Cypress 9.2.0)Fideicommissum
This doesn't really works, yes it passes the test but also passes it when the call is made :)Poe
@JuliusKoronci That should not be the case. If the call is made, an entry is added to interceptions, hence the lenght does not equal 0 anymore. That's how I see it working in my app at least...Selfpropulsion
Works for me too (12.1.0)Senary
It works on 12.7.3. Important is that the passed string includes .all, although I have no idea what that is for.Catherinacatherine
T
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);
});
Terza answered 27/2, 2020 at 19:25 Comment(5)
This abstraction is probably the best among the responses.Wonderland
For anyone wondering if this works with cy.intercept, it doesn't.Yezd
The question is about has NOT been called so, I expect cy.wait("@request") throw with timeout. Am I wrong?Edible
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.Terza
In other words, this doesn't work.Catherinacatherine
P
7

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;
    }
})
Portent answered 27/6, 2018 at 13:49 Comment(3)
This sort of worked. I had to follow the example in the linked duplicate and throw an error instead of using an assertion. Cypress didn't mark the test failed when the assertion occurred.Skyrocket
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!Impostume
Is there a way to check a particular API is called in the Network Tab, when performing some UI operation?Klaipeda
M
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)
})
Mercorr answered 12/12, 2019 at 10:29 Comment(1)
Is there a way to check a particular API is called in the Network Tab, when performing some UI operation?Klaipeda
S
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);
Shu answered 26/10, 2020 at 5:2 Comment(0)
A
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.

Anachronistic answered 10/12, 2019 at 15:43 Comment(2)
Strangely enough, I am now getting this error as well. @Jennifer Shehane what are your thoughts?Impostume
Yeah, I guess I got downvoted for not having enough rep to comment on his answer :(Anachronistic
C
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

Calise answered 28/5, 2020 at 12:35 Comment(1)
I found this on a costumer project, and it broke another requests (even in another spec files).Harilda
T
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);
Tarpeia answered 28/4, 2021 at 14:2 Comment(1)
Worked for me for 0 before call, but not for 1 after call. So, didn't work.Impostume
M
2

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
Melentha answered 15/6, 2021 at 16:13 Comment(2)
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 existPhoto
I think this is the best answer. It's using native and simple assertions and I don't have to manually create a spy.Terrier
E
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

Elroy answered 1/2, 2023 at 15:9 Comment(0)
I
0

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!

Impostume answered 5/12, 2019 at 19:22 Comment(0)
I
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.
Ithnan answered 1/6, 2021 at 19:28 Comment(0)
T
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;
})
Tristan answered 18/7, 2022 at 2:14 Comment(0)
S
0

2024

I'm adding my 2 cents to this long thread with the solution I found. It both works and is relatively clean in my opinion:

Function

import type { Interception } from 'cypress/types/net-stubbing'

const STORE: Record<string, string[]> = {}

export function countRequestsByAlias(alias: string, waitForInMs: number = 0): Cypress.Chainable<number> {
  if (waitForInMs > 0) {
    cy.wait(waitForInMs)
  }

  return cy.get<Interception>(alias).then(interception => {
    if (!STORE[alias]) {
      STORE[alias] = []
    }

    const storedInterceptionIds = STORE[alias]!!
    if (interception && !storedInterceptionIds.includes(interception.id)) {
      storedInterceptionIds.push(interception.id)
      STORE[alias] = storedInterceptionIds
    }

    return storedInterceptionIds.length
  })
}

Command

Cypress.Commands.add('countRequestsByAlias', countRequestsByAlias)

Typings

declare global {
  namespace Cypress {
    interface Chainable {
      countRequestsByAlias(alias: string, waitForInMs?: number): Cypress.Chainable<number>
    }
  }
}

Usage

cy.intercept('POST', '/a/path').as('createSomething')

cy.countRequestsByAlias('@createSomething', 1000).should('be.equal', 0)

cy.click('button')

cy.wait('@createSomething')
cy.countRequestsByAlias('@createSomething').should('be.equal', 1)

cy.click('button')

cy.wait('@createSomething')
cy.countRequestsByAlias('@createSomething').should('be.equal', 2)

Note

I think you can even customize this solution by also abstracting the cy.wait() part within the command function if necessary.

Swallowtail answered 13/6 at 17:10 Comment(0)
M
-2

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.

Maudemaudie answered 26/2, 2023 at 14:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.