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

RTKQ: fetchFn using cross-fetch fails #1271

Closed
RileyMShea opened this issue Jul 8, 2021 · 15 comments
Closed

RTKQ: fetchFn using cross-fetch fails #1271

RileyMShea opened this issue Jul 8, 2021 · 15 comments

Comments

@RileyMShea
Copy link

RileyMShea commented Jul 8, 2021

When using fetchFn from createApi, isomorphic-fetch works but cross-fetch fails.

Both seem to run fine in react without RTKQ. But with cross-fetch+RTKQ, a request that should be:
/some-endpoint
is run in the browser as /[object%20Request].

Possible I'm an idiot and flubbing the cross-fetch import and that's the problem, but I've tried a few
different import/require default/destructured variations and none seem to solve the issue.

...
const { fetch } = require("cross-fetch");  // doesn't work
// const fetch = require("isomorphic-fetch");  // works

// Define a service using a base URL and expected endpoints
export const pokemonApi = createApi({
  reducerPath: "pokemonApi",
  baseQuery: fetchBaseQuery({
    baseUrl: "https://pokeapi.co/api/v2/",
    fetchFn: fetch,
  }),
...

Minimal Codesandbox example

https://codesandbox.io/s/vibrant-resonance-cvf7m?file=/src/services/pokemon.ts

@phryneas
Copy link
Member

phryneas commented Jul 8, 2021

fetchBaseQuery is doing

    const request = new Request(url, config)
    const response = await fetchFn(request)

cross-fetch is using node-fetch and that just does not support Request argument signature fetch(request), they just support fetch(url, [init]).

https://github.com/node-fetch/node-fetch/blob/ffef5e3c2322e8493dd75120b1123b01b106ab23/src/index.js#L34-L37

@RileyMShea
Copy link
Author

RileyMShea commented Jul 8, 2021

So if I understand correctly cross-fetch won't currently work due its lack of support for Request.

I see several references to cross-fetch in the docs and some tests written around it in the repo:
https://github.com/reduxjs/redux-toolkit/search?q=cross

Is that a PR I can make to remove or modify those references to cross-fetch as a valid input to fetchFn?

Also, as far as testing components that use RTKQ with jest, it seems my options are currently:

  • mock out fetch
  • isomorphic-fetch

Is that roughly accurate?

@phryneas
Copy link
Member

phryneas commented Jul 8, 2021

The weird thing is that even node-fetch seems to mention Request: https://github.com/node-fetch/node-fetch#new-requestinput-options

But the source code I linked above seems to indicate otherwise... so I honestly don't really know what's going on there.

@msutkowski
Copy link
Member

The weird thing is that even node-fetch seems to mention Request: node-fetch/node-fetch#new-requestinput-options

But the source code I linked above seems to indicate otherwise... so I honestly don't really know what's going on there.

That's weird - they both worked when I was testing all of these things - we even used cross-fetch in a test. I can investigate if necessary.

@RileyMShea Regarding testing with jest, feel free to look at the test setup for the repo. Jest runs in a node environment, so you're going to have to polyfill... we use node-fetch:

//@ts-ignore
const nodeFetch = require('node-fetch')
//@ts-ignore
global.fetch = nodeFetch
//@ts-ignore
global.Request = nodeFetch.Request
const { server } = require('./src/query/tests/mocks/server')

beforeAll(() => server.listen({ onUnhandledRequest: 'error' }))
afterEach(() => server.resetHandlers())
afterAll(() => server.close())

process.on('unhandledRejection', (error) => {
  // eslint-disable-next-line no-undef
  fail(error)
})

@phryneas
Copy link
Member

phryneas commented Jul 8, 2021

Ah... I think I remember something from the old repo:
rtk-incubator/rtk-query#88 (comment)

It seems that cross-fetch does not set the Request on global.

Doing it by hand fixes that:

import fetch, { Headers, Request, Response } from 'node-fetch';
import AbortController from 'abort-controller';

global.fetch = fetch as any;
global.Headers = Headers as any;
global.Request = Request as any;
global.Response = Response as any;
global.AbortController = AbortController;

@donatoaguirre24
Copy link

donatoaguirre24 commented Jul 10, 2021

The following works fine for me:

jest.setup.js

import AbortController from 'abort-controller';
import { fetch, Headers, Request, Response } from 'cross-fetch';
import { server } from './src/server';

global.fetch = fetch;
global.Headers = Headers;
global.Request = Request;
global.Response = Response;
global.AbortController = AbortController;

beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

server.js

import { setupServer } from 'msw/node';
import { authApiMock } from '@/services/auth/authApiMock';
import { eventsApiMock } from '@/services/events/eventsApiMock';

export const server = setupServer(...authApiMock, ...eventsApiMock);

@RileyMShea
Copy link
Author

@donatoaguirre24 That is a super slick test setup. I was passing isomorphic-fetch to fetchFn and making actual API calls, but this is 100X better. I avoid needing cross-fetch in my bundle and my tests are fast thanks to msw.

Thanks for all the help @phryneas @msutkowski @donatoaguirre24, and to the team for RTK. RTK is so good I feel guilty using it, esp now w/ RTKQ and its open API codegen.

@donatoaguirre24
Copy link

donatoaguirre24 commented Jul 10, 2021

@RileyMShea Pro tip: Just add abort-controller and cross-fetch as dev dependencies so they are not bundled in the production code

@dujuanxian
Copy link

import 'whatwg-fetch'; perfectly solve the problem in node test env using msw

@hhsl
Copy link

hhsl commented Sep 14, 2021

import 'whatwg-fetch'; perfectly solve the problem in node test env using msw

This was the only way how I got my tests running. Thanks for that 👍 (TestingLibrary+MSW+RTKQ+Jest).

But I am still seeing the warning that fetch is not available:

console.warn
    Warning: `fetch` is not available. Please supply a custom `fetchFn` property to use `fetchBaseQuery` on SSR environments.

Which is really puzzling, is this really the way to get fetchBaseQuery to "work" in a Jest environment?

@phryneas
Copy link
Member

It matters when you do that. If fetchBaseQuery is executed before 'whatwg-fetch' is included, you will get that message. It will work anyways, since it tries to access the global fetch later, but technically it is not available in the beginning.

So import order plays a role there

@hhsl
Copy link

hhsl commented Sep 14, 2021

Moved import 'whatwg-fetch'; to the very first line of Jest's setupTests.ts file and the warning was gone 🥳 Thanks!

@embryCODE
Copy link

import 'whatwg-fetch'; perfectly solve the problem in node test env using msw

Wow. 3 hours of my life and that was the solution. Thank you. Testing the frontend is the hardest part of software development. 1 day to build the feature. 1 week if you try to write tests.

@princefishthrower
Copy link

princefishthrower commented Oct 12, 2021

So we've got whatwg-fetch, node-fetch and cross-fetch which are all able to replace the browser's fetch API in a Node environment... either way though they are Node implementations... does anyone know the advantages / disadvantages of one over the other? 😅 Or shall we assume each of them is "correct enough"? 😄

@Serzhs
Copy link

Serzhs commented Jan 6, 2022

Had the same issue and adding

import 'isomorphic-fetch';

into setup file helped for me

lucasgarfield added a commit to lucasgarfield/image-builder-frontend that referenced this issue Feb 7, 2023
We are migrating towards using RTK Query for API calls, and using `msw`
to mock APIs in our tests.

RTK Query's `fetchBaseQuery` requires the use of `fetch` and `Request`.

`fetch()` was added to Node in v18, and the ability to use it in
Jest tests was added to Jest in v28. However, it still cannot be used
with a `jsdom` test environment.

Therefore, it is necessary to add a polyfill. There are several
libraries available for this but many others in this situation have had
success using `whatwg-fetch` and so it was selected somewhat
arbitrarily.

It is also important that `whatwg-fetch` is imported before
`fetchBaseQuery` (otherwise, a nuisance console warning will be issued).
To ensure this happens, it is imported in a Jest setup file.

References:
reduxjs/redux-toolkit#2084
reduxjs/redux-toolkit#1271
jestjs/jest#13834
jg210 added a commit to jg210/spring-experiments that referenced this issue Feb 8, 2024
So far have had to stop jsdom declaring that tests are running in a browser:

mswjs/msw#1786

Fiddle with whatwg-fetch polyfill:

reduxjs/redux-toolkit#1271

Still more things to fix, some presumably a consequence of first issue.
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

9 participants