Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to intercept with cypress ? #999

Open
qlereboursBS opened this issue Oct 15, 2021 · 7 comments
Open

How to intercept with cypress ? #999

qlereboursBS opened this issue Oct 15, 2021 · 7 comments

Comments

@qlereboursBS
Copy link

Description

This is not an issue, this is a discussion.
I set up Mirage on my project which is using Cypress as a testing tool (using your great documentation, thank you for this).
I would like if there's a way to interecept requests with cy.intercept to do some assertions on the request body for instance ?

More details

When running cy.intercept('POST', '/api/users').as('createUser') and then cy.wait('@createUser').then(({ request }) => {}), cypress says that the call is never done, which seems to be normal, because of Mirage.
Do you think that there could be a way to make it work?

Thanks

@IanVS
Copy link
Collaborator

IanVS commented Oct 15, 2021

Personally I'd recommend against asserting on the request itself, and rather focus on asserting that the API responds the way you expect and that your app behaves as you want. This is similar to what Kent C Dodds writes in https://kentcdodds.com/blog/stop-mocking-fetch (although he recommends MSW).

As for your direct question, I'm not sure if it's possible, but I kind of doubt it.

@qlereboursBS
Copy link
Author

TLDR: You're right, it's not a good idea

I started to reply to your message explaining why we're doing this... but in the end I totally agree, it's not maintainable to do this.
In my opinion, we have 3 ways to proceed:

  • Either we handle all the cases in the UI (for instance, we ensure that we can't send a wrong data to the backend that will make it return an error) ==> If we could do this, it wouldn't be necessary to create some tests, there will always be some changes that will break the UI, and this is why I want this kind of tests
  • Either we check the request sent, to ensure that the data is valid ==> It's exactly the same, we can't anticipate every possible bug, and it's even worse because the logic is handled in the tests and not in the UI.
  • Or finally, we send the requests to the real API (because we won't handle all the logic in Mirage's server, otherwise it would take the same time than developing the backend itself).

So I think that we'll try to setup a test environment we can request to have better tests.

However, maybe it could help other people to know if it's possible

@albuquerquecesar
Copy link

Hi folks. I've been using Mirage for 3 years. It's awesome, thanks!

well, I have same question. How we could intercept a requesting using Cypress? I don't want to to mock or assert against a request but I'd like to wait for some requests to have been finished before interacting with UI. Cypress is really fast.

@andreLuis1506
Copy link

Hi folks. I've been using Mirage for 3 years. It's awesome, thanks!

well, I have same question. How we could intercept a requesting using Cypress? I don't want to to mock or assert against a request but I'd like to wait for some requests to have been finished before interacting with UI. Cypress is really fast.

Do you have some solution? i have the same problem

@albuquerquecesar
Copy link

Hi @andreLuis1506 . I was able to handle this situation by adding a queue of mocked requests. I am gonna suppose you are using Cypress + Mirage and you've followed this tutorial for adding a Mirage JS Proxy.

This is my support/index.

Cypress.on('window:before:load', (win) => {
    win.CYPRESS_REQUESTS = [];
    win.handleFromCypress = function(request) {
        const {method, url} = request;
        const logRequest = {start: true, finished: false, url, method};
        win.CYPRESS_REQUESTS.unshift(logRequest);

        return fetch(url, {
            method,
            headers: request.requestHeaders,
            body: request.requestBody,
        }).then((res) => {
            const content = res.headers.get('content-type').includes('application/json') ?
                res.json() :
                res.text();
            return new Promise((resolve) => {
                content.then((body) => {
                    const result = resolve([res.status, res.headers, body]);
                    logRequest.finished = true;
                    return result;
                });
            });
        });
    };
});

As we can a custom request object is added to array attached to window object: win.CYPRESS_REQUESTS.unshift(logRequest). It's added as not finished.

So I've create a function that will look for request given a regex pattern that it's not finished. This function will wait until the target request becomes finished.

function waitForRequest(urlPattern: string, method = '') {
        const wildcardRegex = new RegExp('\\*', 'g');
        const questionMarkRegex = new RegExp('\\?', 'g');

        const replacedWildCard = urlPattern.replace(wildcardRegex, '[^ ]*').replace(questionMarkRegex, '\\?');
        const regexUrl = new RegExp(replacedWildCard, 'g');

        cy.window().then((win) => {
            const checkMatches = (r: any) => {
                let isMatched = r.url.match(regexUrl);

                if (method) {
                    isMatched = isMatched && r.method === method;
                }

                return isMatched;
            };
            const findRequest = () => (win as any).CYPRESS_REQUESTS.find(checkMatches);
            const errorMsg = `[Request [${method}] to ${urlPattern} didn't happen`;

            cy.waitUntil(findRequest, {errorMsg}).its('finished', {timeout: 10000})
                .should('be.true')
                .log(`Request [${method}] to ${urlPattern} has been finished`);
        });
    }

With this help i could simulate intercepting a request and wait for it.
PS: I've been using waitUntil cypress plugin.

so I can intercept requests, filter for a regex pattern and HTTP method (GET,POST etc) and wait for it to becomes finished.

@albuquerquecesar
Copy link

Hi folks. I've been using Mirage for 3 years. It's awesome, thanks!
well, I have same question. How we could intercept a requesting using Cypress? I don't want to to mock or assert against a request but I'd like to wait for some requests to have been finished before interacting with UI. Cypress is really fast.

Do you have some solution? i have the same problem

See my previous answer.

@joshuaskinnerlibdib
Copy link

joshuaskinnerlibdib commented Apr 4, 2023

I was excited to come back to this issue and see @albuquerquecesar's solution. I tried to implement it for my usecase and ran into some issues.

I'm using MirageJS alongside Cypress in its relatively new Component Testing mode. One difference between it and E2E mode that matters in this case is which Cypress events are accessible. CT mode doesn't have access to the window:before:load event. I poked around for an alternative that I could use to grant me access to the window object so things could be wired up to use their solution, but had no luck.

So instead, I just totally skipped the event bit and modified waitForRequest to make use of the Pretender object that is exposed on the MirageJS server instance. I also turned the function into a Cypress command, because why not.

// commands.js
Cypress.Commands.add('waitForRequest', (server, urlPattern, method = '') => {
    const wildcardRegex = /\*/g;
    const questionMarkRegex = /\?/g;

    const replacedWildCard = urlPattern.replace(wildcardRegex, '[^ ]*').replace(questionMarkRegex, '\\?');
    const regexUrl = new RegExp(replacedWildCard, 'g');

    const checkMatches = (r) => {
        let isMatched = r.url.match(regexUrl);

        if (method) {
            isMatched = isMatched && r.method.toLowerCase() === method.toLowerCase();
        }

        return isMatched;
    };

    const requestFound = () => server.pretender.handledRequests.some(checkMatches);
    const errorMsg = `[Request [${method}] to ${urlPattern} didn't happen`;

    return cy
        .waitUntil(requestFound, {errorMsg})
        .should('be.true')
        .log(`Request [${method}] to ${urlPattern} has been finished`);
});
// some-test-file.cy.js

let server;

beforeEach(() => {
 server = createServer();
});

it('a test', () => {
    server.post('/api/v1/endpoint/', () => []);

    cy.mount(<Component {...props} />, {state});

    cy.findByLabelText(/some text/i).select('bar');

    cy.waitForRequest(server, '/api/v1/endpoint/', 'POST');
});

Note that this still depends on the Cypress wait-until plugin, so you'll need to include that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants