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

implement window.fetch capturing/mocking #95

Closed
terinjokes opened this issue Jan 25, 2016 · 119 comments · Fixed by #4176
Closed

implement window.fetch capturing/mocking #95

terinjokes opened this issue Jan 25, 2016 · 119 comments · Fixed by #4176
Assignees
Labels
topic: network type: enhancement Requested enhancement of existing feature

Comments

@terinjokes
Copy link

terinjokes commented Jan 25, 2016

Many developers are migrating to window.fetch from XHRs due to the simplified async interface it exposes. I happen to be one of those developers.

Cypress should support window.fetch with similar abilities to those of XHRs: I should be able to query the request and response, wait on them, and mock with data and fixtures.

Cypress team edit

This feature will work seamlessly once Full network stubbing is implemented.

Workarounds

  • use experimental fetch polyfill available in v4.9.0

or do it yourself:

  • delete window.fetch before visiting the page to force your application to polyfill it on top of XHR. If the application does not include a polyfill, you can load one from your tests.
  • stub window.fetch directly in JavaScript

See these solutions in Stubbing window.fetch example recipe

@brian-mann brian-mann added the type: enhancement Requested enhancement of existing feature label Jan 26, 2016
@jennifer-shehane jennifer-shehane added this to the 0.17.0 milestone Jul 19, 2016
@brian-mann
Copy link
Member

Per the 0.17.0 release at the very least window.fetch will actually work properly now.

It's on our radar to adding stubbing to these as well. We're a lot closer to being able to do it now.

@brian-mann brian-mann modified the milestones: 0.17.x, 0.17.0 Aug 31, 2016
@PhilippSpo
Copy link

I would love to see this feature. I am very excited about stubbing. It will definitely solve a lot of problems when doing these tests. window.fetch is very widely used by now 👍

@thomas-huston-zocdoc
Copy link

My team would love this too! window.fetch is very commonly used in React applications now, so I'm sure other teams would appreciate this as well.

@nwehrle
Copy link

nwehrle commented Jan 16, 2017

Same here. We use fetch for it's simplicity in our react application. Until yet we use our own proxy that mocks the ajax requests, but your XHR approuch is much more readable.

@nwehrle
Copy link

nwehrle commented Jan 16, 2017

Maybe you could offer a way to use fetch outside the service-worker's context first and in a second phase you could add fetch in service-workers?

@jennifer-shehane jennifer-shehane removed this from the 0.17.x milestone Feb 3, 2017
@brian-mann
Copy link
Member

There is now an example of using cy.stub to control window.fetch. It's not the same API as cy.route but it will work today!

We have a bunch of examples of it here: https://github.com/cypress-io/cypress-example-recipes#controlling-behavior-with-spies-stubs-and-clocks

@stigkj
Copy link

stigkj commented Feb 21, 2017

As not all browsers support window.fetch yet, I guess most use some form of polyfill for this. Then one could do something like this:

cy
  .visit('<some url>', {
    onBeforeLoad: (win) => {
      win.fetch = null
    }
  })

After doing this, the polyfill will just use XHR instead of fetch in the test, and one can use the normal Cypress's capturing/mocking tools. What do you think of this, @brian-mann?

@nwehrle
Copy link

nwehrle commented Feb 28, 2017

Thank you for the alternative approach. Will try that.
What is the state of the real implementation? https://github.com/cypress-io/cypress/projects/20

@mvandebunt
Copy link

@stigkj thx for the workaround/solution.
the solution in https://github.com/cypress-io/cypress-example-recipes#controlling-behavior-with-spies-stubs-and-clocks was working for several projects for me.
Now I need to test an app using redux-saga with yield call(POST, { url: '/api/login/users', options }); and there it is not working, your solution is working!

@mukaibot
Copy link

@stigkj thanks for the work around, like @mvandebunt this has worked for me in Chromium 56 :)

I find this a bit easier than the spy approach. I'm looking forward to Cypress getting better support for window.fetch :)

@Graham42
Copy link
Contributor

Graham42 commented Nov 9, 2017

While people are waiting for this, As long as your app falls back to XHR (polyfill or other) you can use this in cypress/support/commands.js to use @stigkj 's solution across all tests

Cypress.Commands.overwrite('visit', (originalFn, url, options) => {
  const opts = Object.assign({}, options, {
    onBeforeLoad: (window, ...args) => {
      window.fetch = null;
      if (options.onBeforeLoad) {
        return options.onBeforeLoad(window, ...args);
      }
    },
  });
  return originalFn(url, opts);
});

@brian-mann
Copy link
Member

brian-mann commented Nov 9, 2017

@Graham42 There is actually an easier way to do this and it will work not only for visits but all page transitions.

https://docs.cypress.io/api/events/catalog-of-events.html#Window-Before-Load

This event is called at the same time the onBeforeLoad callback is invoked.

You also won't have to overwrite the visit method

aduth added a commit to aduth/dones that referenced this issue Nov 19, 2017
Supported natively in Cypress, but not yet for window.fetch

See: cypress-io/cypress#95
@vschoettke
Copy link

vschoettke commented Nov 24, 2017

@Graham42 the overwrite of the visit command does not call onBeforeLoad for me (cypress 1.1.1) what version do you use?

@brian-mann
Copy link
Member

@vschoettke Do not use the visit overwrite - use what I suggested in my comment here: #95 (comment)

There's nothing wrong per-se with overwriting visit but its unnecessary when you can use events which will apply to all page transitions.

@nmchaves
Copy link

nmchaves commented Nov 28, 2017

@brian-mann worked like a charm! I just put this in support/index.js:

// use `Cypress` instead of `cy` so this persists across all tests
Cypress.on("window:before:load", win => {
  win.fetch = null;
});

@gnapse
Copy link

gnapse commented Nov 28, 2017

@nmchaves thanks! Your comment came just right in when I was about to ask if there was a way to make this global instead of inside the tests! You rock man!

@rkistinger
Copy link

rkistinger commented Dec 8, 2017

Thank you @nmchaves !! Make sure you are using the polyfill for this fallback behavior to work

@gnepud
Copy link

gnepud commented Dec 20, 2017

Thank your solution @nmchaves, but with it, i cant get response body by function xhr.responseJSON. Do you have same idea ?

@joyfulelement
Copy link

Has anyone successfully getting the network stub to work with fetch requests made via a page within an iframe?

@siawyoung
Copy link

For anyone looking for stub Apollo/GraphQL queries, I wrote one, as detailed here: https://siawyoung.com/stub-graphql-cypress. The simple (non-batched) version is pasted here for convenience:

const responseStub = ({ payload, ok }) =>
  Promise.resolve({
    json() {
      return Promise.resolve(payload)
    },
    text() {
      return Promise.resolve(JSON.stringify(payload))
    },
    ok: ok === undefined ? true : ok,
  })

Cypress.Commands.add('graphql', stubbedGQLResponses => {
  cy.on('window:before:load', win => {
    const originalFetch = win.fetch
    const fetch = (path, options, ...rest) => {
      if (options.body) {
        try {
          const body = JSON.parse(options.body)
          if (body.operationName in stubbedGQLResponses) {
            const stubbedResponse = stubbedGQLResponses[body.operationName]
            return responseStub(stubbedResponse)
          }
          return originalFetch(path, options, ...rest)
        } catch (e) {
          return originalFetch(path, options, ...rest)
        }
      }
      return originalFetch(path, options, ...rest)
    }
    cy.stub(win, 'fetch', fetch)
  })
})
context('Accounts page', () => {
  beforeEach(() => {
    cy.graphql({
      userAccount: {
        payload: {
          data: {
            user: {
              id: 1,
              first_name: 'Bob',
              last_name: 'Smith',
              __typename: 'User',
            },
          },
        },
      },
    })
    cy.visit('/account')
  })

  it('should display user first and last name correctly', () => {
    cy.get('.first-name').should('have.text', 'Bob')
    cy.get('.last-name').should('have.text', 'Smith')
  })
})

There's also a version that works with batched Apollo requests in the post itself.

@tschoartschi
Copy link

As @valentinogagliardi mentioned Mirage.js is a great tool. We are also using Mirage.js and there is also a guide for Cypress 🎉 (you can find it here) maybe it makes sense to join the forces and create a nice integration of Mirage.js into Cypress. I think @samselikoff would be happy about helping hands.

This issue was opened more than 4 (!) years ago and it's still open. So maybe Mirage.js could be an option for some of use 🙂

@samselikoff
Copy link

We love Cypress and would absolutely love to collaborate here if there's an interest!

@bahmutov
Copy link
Contributor

We have experimental window.fetch polyfill coming in 4.9.0 (opt-in feature) before full network stubbing is ready

@JulienKode
Copy link

We have experimental window.fetch polyfill coming in 4.9.0 (opt-in feature) before full network stubbing is ready

@bahmutov it's really nice, when it will be available in beta ?

@jennifer-shehane
Copy link
Member

jennifer-shehane commented Jun 26, 2020

The experimentalFetchPolyfill configuration option has been released as part of 4.9.0. When this option is true, Cypress will automatically replace window.fetch with a polyfill that Cypress can spy on and stub.

You should be able to remove any of the following code:

Cypress.on('window:before:load', (win) => {	
  delete win.fetch	
})	

or

cy.visit('/', {	
  onBeforeLoad (win) {	
    delete win.fetch	
  },	
})	

And replace it with the below in your configuration file (cypress.json)

{
  "experimentalFetchPolyfill": true
}

You can see a full example of this in use here: https://github.com/cypress-io/cypress-example-recipes/tree/master/examples/stubbing-spying__window-fetch

If you encounter any issues with the new experimentalFetchPolyfill, please open a new issue with a reproducible example.

@stefanoimperiale
Copy link

In my case, the new experimentalFetchPolyfill does not work, it changes completely my request, from the method to the url.
I use can-ndjson-stream library to make the fetch requests, so I rewrote the polyfill file to work in my case. I hope this can be helpful to anybody in the same mine situation: https://gist.github.com/yagudaev/2ad1ef4a21a2d1cfe0e7d96afc7170bc#gistcomment-3358272

@stevenvachon
Copy link

Yeah, it doesn't work for me either. My login fetch gets a 415 (Unsupported Content-Type).

@roomle-build
Copy link

@stefanoimperiale @stevenvachon we are also using MirageJs which works great for every type of mocking network requests. It works fine with fetch and it also has a lot of nice features on top 🙂 so probably you could check out this project. Maybe it helps you to achieve what you need.

@bengry
Copy link

bengry commented Jul 15, 2020

Yeah, it doesn't work for me either. My login fetch gets a 415 (Unsupported Content-Type).

Same here. We use a POST request to make a login, passing in the username and password in the request's body as a JSON object. However, using the experimental polyfill the request headers are changed, and the Content-Type is now text/plain;charset=UTF-8, which our backend server rejects.

I think this happens because we utilize the Headers class in our fetch call, and don't pass a plain object (e.g. { 'Content-Type': 'application/json' }). We do polyfill Headers (using fetch-headers), but seems like the polyfill used by experimentalFetchPolyfill (unfetch) doesn't support them even when they're polyfilled.

This workaround (in our app code) works, although slightly awkward:

fetch(url, {
   ...
-  headers,
+  headers: Object.fromEntries(headers)
})

@baeharam
Copy link

Is it valid to use fetch polyfill called whatwg-fetch to capture fetch ?

@mefechoel
Copy link

@baeharam I did this for a project and it worked just fine.
I just ran into a Problem with very large response payloads.
If the response was bigger tham ~1mb the request failed.
Thats not a problem with the polyfill though, but rather with the way cypress mocks xhr requests.

@baeharam
Copy link

@mefechoel Thanks for your feedback! I'll try whatwg-fetch for my graphql query 😄

@cbstauss
Copy link

Is there a way to only replace window.fetch on stubbed routes?

@cypress-bot cypress-bot bot added stage: work in progress stage: needs review The PR code is done & tested, needs review and removed stage: needs review The PR code is done & tested, needs review stage: work in progress labels Aug 25, 2020
@cypress-bot
Copy link
Contributor

cypress-bot bot commented Sep 1, 2020

Released in 5.1.0.

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

@cypress-bot cypress-bot bot removed the stage: needs review The PR code is done & tested, needs review label Sep 1, 2020
@cypress-bot cypress-bot bot locked as resolved and limited conversation to collaborators Sep 1, 2020
@jennifer-shehane
Copy link
Member

The features requested in this issue are now possible as part of cy.route2().

cy.route2() is currently experimental and requires being enabled by passing "experimentalNetworkStubbing": true through your Cypress configuration. This will eventually be merged in as part of our standard API.

Please see the cy.route2() docs for full details: https://on.cypress.io/route2

If you encounter any issues or unexpected behavior while using cy.route2() we encourage you to open a new issue so that we can work out all the issues before public release. Thanks!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
topic: network type: enhancement Requested enhancement of existing feature
Projects
None yet
Development

Successfully merging a pull request may close this issue.