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

[Cypress 6.0.0] Overriding interceptors doesn't work. #9302

Closed
ar7casper opened this issue Nov 24, 2020 · 55 comments
Closed

[Cypress 6.0.0] Overriding interceptors doesn't work. #9302

ar7casper opened this issue Nov 24, 2020 · 55 comments
Assignees
Labels
topic: cy.intercept() type: unexpected behavior User expected result, but got another

Comments

@ar7casper
Copy link

ar7casper commented Nov 24, 2020

Current behavior

With Version <6.0.0, if you used c.route with a stub, you could then use it again with a new stub and it would take the second fixture for the second request.

With version >6.0.0 It doesn't happen, the override doesn't work with cy.intercept and it always returns the initial fixture.

cy.intercept(
  {
     method: 'GET',
     url: 'items',
  },
   { fixture: 'items/empty-list.json' }
);

// Some more testing of the app, and then fetching again -

cy.intercept(
  {
     method: 'GET',
     url: 'items',
  },
   { fixture: 'items/list-1.json' }
);

The use case is very simple.
Imagine my app loads with 0 items, using a GET.
Then, I do some stuff, do a POST, and then GET those items again.
I want to stub it with [item], instead of the initial [].

So what happens is that it always return [], not [item] as it used to.
I'm quite sure it's not a problem with my code, used to work perfectly fine till today.

The first image is the initial intercept, you can see it gets 2
image
The second image is what is shown for the second GET, the one that should've matched and return [item]
image

Desired behavior

I want to be able to override interceptors

@ar7casper ar7casper changed the title Overriding interceptors doesn't work. [Cypress 6.0.0] Overriding interceptors doesn't work. Nov 24, 2020
@cypress-bot cypress-bot bot added the stage: proposal 💡 No work has been done of this issue label Nov 24, 2020
@filipemir
Copy link

I was really excited to use the new intercept ability to be able to dynamically respond to requests with different data based on query params or other URL-specific data. But this omission is even worse than the original problem: it altogether prevents us from testing failure cases. For example: testing that the UI responds as expected to when a given request responds with a 200 vs when it responds with a 404. Is this not an expected use case? Is there an intended workaround I'm missing?

@rinero-bigpanda
Copy link

I believe this is the crux of the issue

export function getRouteForRequest (routes: BackendRoute[], req: CypressIncomingRequest, prevRoute?: BackendRoute) {
const possibleRoutes = prevRoute ? routes.slice(_.findIndex(routes, prevRoute) + 1) : routes
return _.find(possibleRoutes, (route) => {
return _doesRouteMatch(route.routeMatcher, req)
})
}

Assuming I understand everything correctly - it looks like the order by which the routes are being matched is the original order they're pushed in.

Changing the code to this might solve it

 export function getRouteForRequest (routes: BackendRoute[], req: CypressIncomingRequest, prevRoute?: BackendRoute) { 
   const possibleRoutes = prevRoute ? routes.slice(_.findIndex(routes, prevRoute) + 1) : routes 
  
-   return _.find(possibleRoutes, (route) => { 
+   return _.find(possibleRoutes.slice().reverse(), (route) => { 
     return _doesRouteMatch(route.routeMatcher, req) 
   }) 
 } 

From what I could tell, previously it let the driver do this matching instead

// loop in reverse to get
// the first matching stub
// thats been most recently added
for (let i = routes.length - 1; i >= 0; i--) {
const route = routes[i]
if (server.xhrMatchesRoute(xhr, route)) {
return route
}
}

Unfortunately, I don't have much time setting up a local environment to test & verify that it's indeed the issue - if anyone wants to do it they're more than welcome to use my code diff :)

@cypress-bot cypress-bot bot added stage: work in progress stage: needs review The PR code is done & tested, needs review and removed stage: proposal 💡 No work has been done of this issue stage: work in progress labels Nov 26, 2020
@jennifer-shehane jennifer-shehane added the type: unexpected behavior User expected result, but got another label Dec 3, 2020
@Romanchuk
Copy link

Romanchuk commented Dec 4, 2020

This issue blocks me to update to 6.0.0 version

@ThatGuyHS
Copy link

Just wanted to add that I am also blocked from updating due to this.

@flotwig
Copy link
Contributor

flotwig commented Dec 7, 2020

This issue blocks me to update to 6.0.0 version

@Romanchuk @ThatGuyHS this shouldn't block you from upgrading to 6.0.0, using cy.intercept is entirely optional

@Romanchuk
Copy link

@flotwig Yes, but for me cy.intercept is an only reason to upgrade

@raphaelmatori
Copy link

raphaelmatori commented Dec 10, 2020

+1

Once cy.intercept() is declared for some route, you can not override its behaviour. It is quite annoying and until fixed will block me to update since we usually mock a '200' response, but we also need to test other ones.

Looking forward a pull request to fix it.

@jennifer-shehane
Copy link
Member

We are currently looking into ways to make this possible with cy.intercept() while still maintaining the value of the other features introduced in cy.intercept(). There's some investigation we need to do to ensure all use cases can be covered in a way that makes sense.

@samijaber
Copy link

For the future, it would be useful to mention this critical breaking change in your CHANGELOG or migration guides. All of these resources imply that cy.intercept is pretty much a drop-in replacement for cy.route:

There is nothing indicating the issues mentioned in this thread. I recommend you adjust both articles above and mention this difference for others attempting to upgrade.

@js1m
Copy link

js1m commented Dec 16, 2020

It is indeed a huge blocker. And what @samijaber said:

Inability to overwrite or clear an interceptor should be explicitly stated in the docs.

In my case, I am testing my application to re-authenticate after a failing request and then to retry that failed request with the new credentials. No way to do that if I cannot override or clear the previous interceptor.

@manojhans
Copy link

It also breaks the below cases which were working with cy.route():

Before:

cy.route('POST', **/ticket/search*, { fixture: 'search.json' }).as('search');
cy.wait('@search').wait('@search').get('@search.all').should('have.length', 2);

After:

cy.intercept('POST', **/ticket/search*, { fixture: 'search.json' }).as('search');
cy.wait('@search').wait('@search').get('@search.all').should('have.length', 2);

Results:
Screenshot 2020-12-16 at 16 15 09

Before:

cy.route('POST', **/ticket/search*, { fixture: 'search.json' }).as('search');
cy.get('@search').its('response.statusCode').should('eq', 200);

After:

cy.intercept('POST', **/ticket/search*, { fixture: 'search.json' }).as('search');
cy.get('@search').its('response.statusCode').should('eq', 200);

Results:
Screenshot 2020-12-16 at 16 10 45

though it's working fine with below code:

cy.intercept('POST', **/ticket/search*, { fixture: 'search.json' }).as('search');
cy.wait('@search').its('response.statusCode').should('eq', 200);

but not with this:

cy.intercept('POST', **/ticket/search*, { fixture: 'search.json' }).as('search');
cy.wait('@search');
cy.get('@search').its('response.statusCode').should('eq', 200);

Also, is there any way to get the last call of the same endpoint? something like this:

cy.wait('@search').last().its('request.body').should('deep.equal', {
key: 'Something',
});

@nicklemmon
Copy link

Huge thanks for the Cypress team for all of their hard work on this and other new features. Complex feature changes and new APIs always have some rough edges. Always impressed with the quality of your work. 👏

@jennifer-shehane
Copy link
Member

@manojhans The issue with cy.get() returning null was fixed in 6.2.0: #9306

@manojhans
Copy link

Thanks @jennifer-shehane. It's working fine in 6.2.0

ErikMichelson added a commit to hedgedoc/react-client that referenced this issue Feb 18, 2021
The tests are still failing, because we can't redefine the intercept of the config.
A workaround needs to be looked for.
See issue: cypress-io/cypress#9302

Signed-off-by: Erik Michelson <github@erik.michelson.eu>
davidebriani pushed a commit to davidebriani/astarte-dashboard that referenced this issue Feb 19, 2021
- Replace cy.route() commands with cy.intercept() alternative
- Removed cy.server() commands
- Add a 'cy.dynamicIntercept()' command to save and overwrite network
mocks. For more details, see issue cypress-io/cypress#9302

Signed-off-by: Davide Briani <davide.briani@ispirata.com>
@japhex
Copy link

japhex commented Feb 21, 2021

This would seriously help us loads with our suite. Our example is a graphql single endpoint, and I want to change certain responses dependent on when we fire mutations. e.g. when we load a table, load with intiial mock data, after firing a successful create record mutation then responding with a new set of mock data to include that record. Structure wise within our tests and app I'm really happy with it, it just all relies on this being possible!

@witeck
Copy link

witeck commented Feb 24, 2021

If you want to make the answer dependent on the parameter value, this example might be helpful too:

this.replays = {};

cy.fixture('q1-response.json').then((fixture) => {
	this.replays['qValue1'] = {body: fixture};
});
cy.fixture('q2-response.json').then((fixture) => {
	this.replays['qValue2'] = {body: fixture};
});

cy.intercept({method: 'POST', url: 'my/url'}, (req) => {
	const urlParams = new URLSearchParams(req.body);
	const qValue = urlParams.get('q');
	req.reply(this.replays[decodeURI(qValue)]);
}).as("answer");

The way to extract q parameter may differ depending on how it was passed.

ErikMichelson added a commit to hedgedoc/react-client that referenced this issue Feb 28, 2021
The tests are still failing, because we can't redefine the intercept of the config.
A workaround needs to be looked for.
See issue: cypress-io/cypress#9302

Signed-off-by: Erik Michelson <github@erik.michelson.eu>
@rinero-bigpanda
Copy link

I'm in the processes of doing a user-land workaround but it's still not fully functional.
That's what I was able to do so far:

// This is called from the `support/index.js` file
beforeEach(() => {
  const staticMimeTypes = ['text/css', 'text/html', 'text/javascript', 'font/otf', 'font/ttf', 'font/woff', 'font/woff2', 'image/avif', 'image/webp', 'image/apng', 'image/svg+xml', 'image/*'];
  const staticFileExtensions = ['\\.html$', '\\.css$', '\\.js$', '\\.map$', '\\.md$', '\\.woff$', '\\.woff2$', '\\.ttf$', '\\.eot$', '\\.jpg$', '\\.jpeg$', '\\.png$', '\\.svg$'];
  const methods = ['GET', 'POST', 'DELETE', 'PUT', 'PATCH', 'OPTIONS'];
  const ignoredDomains = ['content-autofill.googleapis.com', 'safebrowsing.googleapis.com', 'accounts.google.com', 'clientservices.googleapis.com'];

  cy.intercept({
    url: /.*/,
  }, (req) => {

    if (staticMimeTypes.some(mime => (req.headers.accept || '').includes(mime))
        || staticFileExtensions.some(extension => req.url.match(extension))
        || ignoredDomains.some(url => req.url.includes(url))
        || !req.method
        || methods.every(method => req.method !== method)) {
      req.reply();
    } else {
      const mocks = cy.state('__INTERNAL_MOCK_DEFINITIONS__');
      const mock = mocks.find(mock => mock.method === req.method && Cypress.minimatch(req.url, mock.url));
      if (mock) {
        req.reply(mock.status, mock.response);
      } else {
        throw new Error(`Route not stubbed.\nPlease add a cy.route() for url: "${req.url}" and method "${req.method}".`)
      }
    }
  });
});

Then, I also have a mock command that looks like this:

Cypress.Commands.add('mock', { prevSubject: true }, ({ method, url, response, status, headers }) => {
  cy.state('__INTERNAL_MOCK_DEFINITIONS__', [{
    method: method || 'GET',
    url,
    response,
    status,
    headers
  }].concat(cy.state('__INTERNAL_MOCK_DEFINITIONS__') || []));
});

this allows me to do something like this:

  cy.wrap({
    method: 'POST',
    url: '**/login',
    status: 200,
    response: {
      data: {}
    },
  }).mock();

The reason this "works" is because I have just one route handler that matches everything, then, in that handler I check the routes defined through mock but because of this command works it checks the routes in reverse order, takes the first one and returns the reply defined in it.
Basically what's done here is that I'm implementing the route matching logic by myself.

There are problems though, as you can see, there are a bunch of urls I need to ignore because they're called by the browser itself that for some reason are also being intercepted here 😳

In addition, sometimes requests aren't being intercepted here even though they should, and it passes through to the underlying server. (in my case the server doesn't actually know about these routes and it responds with a 404).

Also, it looks like it needs to "warm up" because when I use cypress open, initial run fails spectacularly but then I hit the re-run button it works. If I use cypress run it always fails 🙄

If anyone makes progress with this kind of workaround I'd be happy to know about :)
Either this or implementing an override mechanism within cy.intercept but I'd need something to upgrade to Cypress@6 because that's the only reason for me to upgrade.

@spamsch
Copy link

spamsch commented Mar 8, 2021

How about this quick monkey patch to solve the issue until an official solution is provided? Works for me with 6.6.0. Just put the following into your commands.js. Not sure about side effects but studying the code did not reveal any downsides of implementing a clearIntercept that way.

Assume you have defined your intercept as follows

cy.intercept('GET', '.netlify/functions/subjects/[0-9]*').as('GET-one-subject')

and calling it like that

cy.wait('@GET-one-subject')

then you can clear it with

cy.clearIntercept('GET-one-subject')

Here is the command

Cypress.Commands.add('clearIntercept', (aliasName) => {
  const routes = Cypress.state('routes')
  Object.entries(routes).forEach(([key, value]) => {
    const { alias } = value
    if (Object.is(alias, aliasName)) {
      delete routes[key]
    }
  })
  Cypress.state('routes', routes)
})

Update: This does not quite work as expected in all cases. Needs more investigation as more elements of the state seem to be touched.

swhmirror pushed a commit to SoftwareHeritage/swh-web that referenced this issue Mar 9, 2021
cy.route has been deprecated in cypress 6.0 in favor of cy.intercept
and will be removed soon.

So migrate code stubbing network requests to the intercept API.

The only drawback of the intercept API is that currently stubbed responses
can not be overridden but hopefully a simple workaround can be used
(see cypress-io/cypress#9302).
@HendrikThePendric
Copy link

We are currently looking into ways to make this possible with cy.intercept() while still maintaining the value of the other features introduced in cy.intercept(). There's some investigation we need to do to ensure all use cases can be covered in a way that makes sense.

(11th December 2020)

At this point in time, are you able to say wether interceptors will eventually get overriding behaviour @jennifer-shehane?

I am asking because this issue is preventing us from rolling out some internal tooling using cy.intercept. If this is eventually going to be implemented, we can wait, but if not we should start building a workaround in our tooling.

@chris5marsh
Copy link

chris5marsh commented Mar 10, 2021

We've been running into this problem as well - trying to return different stubs for the same request, so we can test different responses.

Our solution is to use Cypress.config() to set a variable in each different test, and then check for that config variable in cy.intercept.

In my-test.spec.ts:

it('should return response One', () => {
  Cypress.config('request_type', 'type_one');
  cy.visit('/some-page'); // This page makes a GET call to /some-api-route
  cy.wait('@getResponseOne');
});

it('should return response Two', () => {
  Cypress.config('request_type', 'type_two');
  cy.visit('/some-page'); // This page makes a GET call to /some-api-route
  cy.wait('@getResponseTwo');
});

And in our intercepts.js file:

cy.intercept(
  {
    url: '/some-api-route',
    method: 'GET',
  },
  (req) => {
    const requestType = Cypress.config('request_type');
    if (requestType === 'type_one') {
      req.alias = 'getResponseOne';
      req.reply({ fixture: 'response-one.json' });
    } else if (requestType === 'type_two') {
      req.alias = 'getResponseTwo';
      req.reply({ fixture: 'response-two.json' });
    }
  },
);

Values set by Cypress.config() in a spec file are only in scope for the current spec file, so you don't need to worry about polluting the global config scope.

Hopefully there will be a way to clear/override intercepts introduced soon, but until then this method seems to work well for us!

@FrancoisCamus
Copy link

Is the improvement already being worked on in PR #14543 ?

@flotwig
Copy link
Contributor

flotwig commented Mar 11, 2021

@FrancoisCamus indeed :) Feel free to take a look at the issue comment in #14543 and let me know if it will cover your needs.

@ejpg
Copy link

ejpg commented Mar 17, 2021

What about this workaround? is working for me with cypress 6.2.0

let fixture

beforeEach(() => {
    fixture = 'one.json'

    cy.intercept('GET', '/some-api-route', (req) => {
	req.reply({ fixture })
    })

})

When('I need the second fixture', () => {
    fixture = 'two.json'

    ...
})

nicholaschiang added a commit to tutorbookapp/tutorbook that referenced this issue Mar 17, 2021
Cypress v6 doesn't support overriding `cy.intercept` routes. Instead, we
have to define them individually within each tests that they're
referenced in order to prevent scope issues.

See: cypress-io/cypress#9302
@Tintef
Copy link

Tintef commented Mar 18, 2021

In my case using a global variable to change the fixture wasn't a solution, as I need to return a different fixture in the same test (I tried with @chris5marsh and @ejpg solutions with no luck).

What works for me is using a routeHandler and deciding which fixture to return depending on the request body/query params:

    cy.intercept({ url: <your-url-here>, method: <your-method-here> }, (req) => {
      if (req.body.direction === 'desc') {
        req.reply({ fixture: 'sorted.data.json' });
      } else {
        req.reply({ fixture: 'data.json' });
      }
    }).as('students');
    
    // Note: in this case, the method is `POST`.

@LirSegev
Copy link

@Tintef What works for me is using a global variable and changing it from within the test (in a 'then' callback)

let fixture = 'default'
beforeEach(() => {
  cy.intercept('url', (req) => {
    req.reply((res) => {
       res.send({ fixture: fixture });
     });
  });
});

it('test', () => {
   // fixture is 'default'
  cy.get('button.submit').then(() => {
    fixture = 'other_fixture';
  });
  // fixture is 'other_fixture'
});

@Tintef
Copy link

Tintef commented Mar 23, 2021

@LirSegev That didn't work for me for some reason 🤷‍♂️

Anyway, I ended up not using cy.intercept and using Mock Service Worker (as overriding those mocks where easier than my way above)

@Gu7z
Copy link

Gu7z commented Mar 25, 2021

My way to workaround this for now is creating two global vars, one for the response itself and another to change the httpStatus.
After the first response, the code change the var value.
Here is an example:

let flag = false
let httpStatus = 200

describe('testing the page', () => {

  it('should verify something', () => {

    cy.intercept('/flag', (req) => {
      req.reply((res) => {
        res.send(httpStatus, {
          successful: true,
          flag
        })

        flag = true
        httpStatus = 202
      })
    })

    // The test which need two different values from /flag request
  })

})

@cypress-bot cypress-bot bot added stage: pending release and removed stage: needs review The PR code is done & tested, needs review labels Mar 26, 2021
@cypress-bot
Copy link
Contributor

cypress-bot bot commented Mar 26, 2021

The code for this is done in cypress-io/cypress#14543, but has yet to be released.
We'll update this issue and reference the changelog when it's released.

@flotwig flotwig closed this as completed Mar 26, 2021
@cypress-bot
Copy link
Contributor

cypress-bot bot commented Apr 5, 2021

Released in 7.0.0.

This comment thread has been locked. If you are still experiencing this issue after upgrading to
Cypress v7.0.0, please open a new issue.

@cypress-bot cypress-bot bot locked as resolved and limited conversation to collaborators Apr 5, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
topic: cy.intercept() type: unexpected behavior User expected result, but got another
Projects
None yet
Development

Successfully merging a pull request may close this issue.