7

I hope you're doing well. I'm currently working on the upgrade of cypress to 7.0. (v7.4.0 more exactly) I have an issue with the overriding of intercept calls.

It seems that Cypress team worked on the overriding problem https://github.com/cypress-io/cypress/pull/14543 (issue : https://github.com/cypress-io/cypress/issues/9302), but it doesn't work for me.

BREAKING CHANGE: Request handlers supplied to cy.intercept are now matched starting with the most-recently-defined request interceptor. This allows users to override request handlers by calling cy.intercept again. This matches the previous behavior that was standard in cy.route.

My first call deals with 2xx responses (I mock it myself)

cy.intercept('GET', 'sameUrl', {
statusCode: 2xx
}

but then I need another intercept with the same url but a different status :

cy.intercept('GET', 'sameUrl', {
statusCode: 4xx
}

I tried using middleware : A new option, middleware, has been added to the RouteMatcher type. If true, the supplied request handler will be called before any non-middleware request handlers.

cy.intercept({ method: 'GET', url: 'sameUrl', middleware: true}, req => {
    req.continue(res => {
         res.statusCode = 4xx
    });
}

But it didn't work, the first intercept is always the one being called. Please if you have any idea what I did wrong/another solution I'm all ears !

Fujo DJ
  • 73
  • 1
  • 5

1 Answers1

4

If I make a minimal example app + test, it follows the rules you quoted above.

Test

it('overrides the intercept stub', () => {

  cy.visit('../app/intercept-override.html')
  
  cy.intercept('GET', 'someUrl', { statusCode: 200 })

  cy.get('button').click()

  cy.get('div')
    .invoke('text')
    .should('eq', '200')                               // passes

  cy.intercept('GET', 'someUrl', { statusCode: 404 })

  cy.get('button').click()

  cy.get('div')
    .invoke('text')
    .should('eq', '404')                               // passes
})

App

<button onclick="clicked()">Click</button>
<div>Result</div>
<script>
  function clicked() {
    fetch('someUrl').then(response => {
      const div = document.querySelector('div')
      div.innerText = response.status
    })
  }
</script>

So, what's different in your app?


Test revised according to comments

Main points that break the test

  • unique aliases are needed
  • the string-based url (2nd test) requires minimatch wildcard prefix ** to work
beforeEach(() => {
  cy.intercept('GET', /api\/test-id\/\d+/, { statusCode: 200 })
    .as('myalias1')
  cy.visit('../app/intercept-overide.html')
})

it('sees the intercept stub status 200', () => {
  cy.get('button').click()
  cy.wait('@myalias1')
  cy.get('div')
    .invoke('text')
    .should('eq', '200')
})

it('sees the intercept stub status 404', () => {
  cy.intercept('GET', '**/api/test-id/1', { statusCode: 404 })
    .as('myalias2')

  cy.get('button').click()
  cy.wait('@myalias2')
  cy.get('div')
    .invoke('text')
    .should('eq', '404')
})

App

<button onclick="clicked()">Click</button>
<div>Result</div>
<script>
  function clicked() {
    fetch('/api/test-id/1').then(response => { 
      // logs as "http://localhost:53845/api/test-id/1" in the Cypress test runner
      const div = document.querySelector('div')
      div.innerText = response.status
    })
  }
</script>

Minimatch

The 2nd intercept uses a string which will match using minimatch. Use this code to check the intercept will work with your app's URL

Cypress.minimatch(<full-url-from-app>, <url-in-intercept>)  // true if matching 
Richard Matsen
  • 20,671
  • 3
  • 43
  • 77
  • 1
    Hello, thank you for your reply. here are some more details about what's implemented in my app. So both calls are created elsewhere (a mock service file) and they have the same Alias. In my test file, the 2xx one is called in the `beforeEach()` and the second in an actual `it` test. Also, I wanted to add that if I kept cy.route() in the 2nd call (with statusCode 4xx), it worked fine ! That's why I was thinking it might be a cy.intercept() overriding problem... – Fujo DJ Jun 07 '21 at 11:53
  • Also the alias is used to `wait` the call only, so I don't think it has to do anything with this issue. – Fujo DJ Jun 07 '21 at 12:06
  • Thank you again for your reply, I tried giving them unique alias names, but now it fails on the `wait` instruction and says that `Timed out retrying after 5000ms: cy.wait() timed out waiting 5000ms for the 1st request to the route: (alias of that request). No request ever occurred`. It's weird because I have the same code you described... – Fujo DJ Jun 07 '21 at 13:49
  • I should probably mention this as well; the 2xx request has a regex url such as `/api\/test-id\/\d+/` and the 4xx request is a string with the id filled in (we take it as a param from the method) `/api/test-id/${id}`, maybe cypress deals differently with regex in this version – Fujo DJ Jun 07 '21 at 14:12
  • I've revised the test again, the next change is to add a wildcard prefix to the 2nd intercept (the one that uses a string). I'm not sure this exactly mirrors your scenario, the fetch in the app may be different so the pattern to match it may also vary. See note about minimatch. – Richard Matsen Jun 08 '21 at 11:26
  • Thank you ! it was actually an issue with the url... the first one always matched the call from the app (as a regex) so it was always called, but the 2nd one had an input that wasn't correctly encoded so it's like it didn't mock anything... I guess `route` encoded inputs and intercept doesn't ? Anyway this solved my use case so thank you a lot – Fujo DJ Jun 08 '21 at 12:21