Reputation: 14447
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
Reputation: 6900
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:
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
})
}
Cypress.Commands.add('countRequestsByAlias', countRequestsByAlias)
declare global {
namespace Cypress {
interface Chainable {
countRequestsByAlias(alias: string, waitForInMs?: number): Cypress.Chainable<number>
}
}
}
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)
I think you can even customize this solution by also abstracting the cy.wait()
part within the command function if necessary.
Upvotes: 0
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
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
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
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
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
Reputation: 7180
I think I found a way that works for me the way I expected, using cy.intercept
and cy.state
.
cy.intercept
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.cy.state
method.Upvotes: 0
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
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
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
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
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
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
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
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
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
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