Skip to content

Commit fb79627

Browse files
Shrugsyphryneas
andauthored
🐛 Prevent closure over fetch (#1267)
Co-authored-by: Lenz Weber <[email protected]>
1 parent 4b8d93d commit fb79627

File tree

3 files changed

+244
-11
lines changed

3 files changed

+244
-11
lines changed

packages/toolkit/src/query/fetchBaseQuery.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@ export interface FetchArgs extends CustomRequestInit {
2727
validateStatus?: (response: Response, body: any) => boolean
2828
}
2929

30+
/**
31+
* A mini-wrapper that passes arguments straight through to
32+
* {@link [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)}.
33+
* Avoids storing `fetch` in a closure, in order to permit mocking/monkey-patching.
34+
*/
35+
const defaultFetchFn: typeof fetch = (...args) => fetch(...args)
36+
3037
const defaultValidateStatus = (response: Response) =>
3138
response.status >= 200 && response.status <= 299
3239

@@ -118,7 +125,7 @@ export type FetchBaseQueryMeta = { request: Request; response: Response }
118125
export function fetchBaseQuery({
119126
baseUrl,
120127
prepareHeaders = (x) => x,
121-
fetchFn = fetch,
128+
fetchFn = defaultFetchFn,
122129
...baseFetchOptions
123130
}: FetchBaseQueryArgs = {}): BaseQueryFn<
124131
string | FetchArgs,
@@ -127,6 +134,11 @@ export function fetchBaseQuery({
127134
{},
128135
FetchBaseQueryMeta
129136
> {
137+
if (typeof fetch === 'undefined' && fetchFn === defaultFetchFn) {
138+
console.warn(
139+
'Warning: `fetch` is not available. Please supply a custom `fetchFn` property to use `fetchBaseQuery` on SSR environments.'
140+
)
141+
}
130142
return async (arg, { signal, getState }) => {
131143
let {
132144
url,

packages/toolkit/src/query/tests/fetchBaseQuery.test.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,34 @@ describe('fetchFn', () => {
531531

532532
expect(request.url).toEqual(`${baseUrl}/echo?apple=fruit`)
533533
})
534+
535+
test('respects mocking window.fetch after a fetch base query is created', async () => {
536+
const baseUrl = 'http://example.com'
537+
const baseQuery = fetchBaseQuery({ baseUrl })
538+
539+
const fakeResponse = {
540+
ok: true,
541+
status: 200,
542+
text: async () => `{ "url": "mock-return-url" }`,
543+
clone: () => fakeResponse
544+
}
545+
546+
const spiedFetch = jest.spyOn(window, 'fetch');
547+
spiedFetch.mockResolvedValueOnce(fakeResponse as any);
548+
549+
const { data } = await baseQuery(
550+
{ url: '/echo' },
551+
{
552+
signal: new AbortController().signal,
553+
dispatch: storeRef.store.dispatch,
554+
getState: storeRef.store.getState,
555+
},
556+
{}
557+
)
558+
expect(data).toEqual({url: 'mock-return-url'})
559+
560+
spiedFetch.mockClear();
561+
})
534562
})
535563

536564
describe('FormData', () => {

0 commit comments

Comments
 (0)