Update: You can now to this with Cypress new intercept
command 🎉: https://www.cypress.io/blog/2020/11/24/introducing-cy-intercept-next-generation-network-stubbing-in-cypress-6-0/
Recently, as part of refactoring on how we handle mocking Cypress network request, I had to find a way to mock the same request made multiple times but with different responses in our test suite.
At the moment, with Cypress, there is no way you can make the mock results dynamic depending on what was requested.
The problem
We have a POST request made to a very simple endpoint. Depending on the request, we get different results.
We already have a very long file with non-formatted JSON responses that are linked to the request using a matching object to match the request payload to a specific answer dynamically. So we already have some of the logic here, which will take ages to extract or break down. Ideally, we want to reuse this.
Some of the cypress default commands were overwritten ( routes
and visit
) to handle this case, as well as mocking fetch.
We moved away from this and removed those to use the default cypress commands. We are using the trick describe here to mock fetch. Now we need to handle the dynamic stubbing part as well.
The solution
This is inspired from a comment on this Cypress issue on GitHub related allowing dynamic stubbing.
We have added a new command to mock request to our endpoint dynamically on-demand using xhook (library to intercept and modify XHR request and responses) :
// commands.js
Cypress.Commands.add('mockArticlesRequest', () => {
Cypress.once('window:before:load', window => {
const script = window.document.createElement('script');
script.onload = function() {
window.xhook.after((request, response) => {
const { method, url, body } = request;
if (
method === 'POST' &&
(url.endsWith('/articles'))
) {
const articlesResponses = require('../fixtures/responses.json');
const parsedBody = JSON.parse(body);
const newResponse = articlesResponses.find(({ matches }) =>
Object.keys(matches).every(
key =>
JSON.stringify(parsedBody[key]) === JSON.stringify(matches[key])
)
);
if (!newResponse) {
return;
}
response.data = JSON.stringify(newResponse.response);
response.text = JSON.stringify(newResponse.response);
}
});
};
script.src = '//unpkg.com/xhook@latest/dist/xhook.min.js';
script.id = 'xhook';
window.document.head.appendChild(script);
});
});
I'm using Cypress.once
so xhook script is not added to other tests on page load.
Then in a test file where calls to that endpoint will be made, we use
// articles.spec.js
describe('Articles page', () => {
beforeEach(() => {
cy.mockArticlesRequest();
cy.server();
cy.route('/user', 'fixture:user.json');
...
});
});
And this what the fixture response file look like:
// fixtures/responses.json
{
"matches": {
"id": 1
},
"response": {
"data": [{
...
}]
}
}
And that's it 🎉 Happy testing 🚀
Note: There seems to an issue where this doesn't work when cy.clock
is used.
Top comments (6)
You can replace the command.js with this?
const apolloResponses = require('../fixtures/responses.json');
beforeEach(() => {
cy.server({ method: 'POST' })
cy.route('**/articles', apolloResponses )
})
I don't think this fit what we needed to achieve. With this,
apolloResponses
is going to be returned as the response for all request whether we wantapolloResponses
to have multiple responses and be matched against the request payload parameters dynamically.(
apolloResponses
was meant to bearticlesResponses
in this example 🤦♀️, I updated that.)What I meant to say is can you do away with the external dependency of xhook and use the cypress api instead:
beforeEach(() => {
cy.server({ method: 'POST' })
cy.route({
method: 'POST',
url: '**/articles',
onResponse: (xhr) => {
// insert your JSON filtering code here
}
})
})
docs.cypress.io/api/commands/route...
That was the first thing I tried but that didn't work for this use case. I wish it did.
Hi, a lot of times its not calling my Cypress.Commands.add("mockArticlesRequest"
is it happening to you? sometimes pass my tests correctly, sometime not.
Thanks
Hi. No, it's been working perfectly. When are you calling the command? What error do you get when it's failing?