Chris Bier
Chris Bier

Reputation: 14447

Is there a way to assert that a route has not been called in Cypress?

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?

Upvotes: 46

Views: 29414

Answers (17)

Ivan Gabriele
Ivan Gabriele

Reputation: 6900

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.

Upvotes: 0

Giovanni5454
Giovanni5454

Reputation: 31

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.

Upvotes: -2

Jan Tancibok
Jan Tancibok

Reputation: 143

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

Upvotes: 2

humble_barnacle
humble_barnacle

Reputation: 490

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;
})

Upvotes: 0

Stiegi
Stiegi

Reputation: 1805

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

Upvotes: 13

user16095626
user16095626

Reputation:

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

Upvotes: 2

Jason Lydon
Jason Lydon

Reputation: 7180

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.

Upvotes: 0

Alessandro DG
Alessandro DG

Reputation: 39

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);

Upvotes: 3

GN.
GN.

Reputation: 9839

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);

Upvotes: 4

Barnaby
Barnaby

Reputation: 977

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)
})

Upvotes: 5

Izhaki
Izhaki

Reputation: 23586

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

Upvotes: 3

SleepWalker
SleepWalker

Reputation: 1261

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);
});

Upvotes: 8

guillaumepn
guillaumepn

Reputation: 323

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);

Upvotes: 18

Coolei Whistles
Coolei Whistles

Reputation: 97

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.

Upvotes: 3

Jonathan Tuzman
Jonathan Tuzman

Reputation: 13262

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!

Upvotes: 0

Feodor Kudinov
Feodor Kudinov

Reputation: 87

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;
    }
})

Upvotes: 7

Jennifer Shehane
Jennifer Shehane

Reputation: 6905

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.

Upvotes: 25

Related Questions