source: node_modules/@reduxjs/toolkit/src/query/tests/devWarnings.test.tsx

Last change on this file was a762898, checked in by istevanoska <ilinastevanoska@…>, 5 months ago

Added visualizations

  • Property mode set to 100644
File size: 17.0 KB
Line 
1import { noop } from '@internal/listenerMiddleware/utils'
2import { configureStore } from '@reduxjs/toolkit'
3import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
4
5const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(noop)
6
7const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(noop)
8
9beforeEach(() => {
10 vi.stubEnv('NODE_ENV', 'development')
11})
12
13afterEach(() => {
14 vi.unstubAllEnvs()
15 vi.clearAllMocks()
16})
17
18afterAll(() => {
19 vi.restoreAllMocks()
20 vi.unstubAllEnvs()
21})
22
23const baseUrl = 'https://example.com'
24
25function createApis() {
26 const api1 = createApi({
27 baseQuery: fetchBaseQuery({ baseUrl }),
28 endpoints: (builder) => ({
29 q1: builder.query({ query: () => '/success' }),
30 }),
31 })
32
33 const api1_2 = createApi({
34 baseQuery: fetchBaseQuery({ baseUrl }),
35 endpoints: (builder) => ({
36 q1: builder.query({ query: () => '/success' }),
37 }),
38 })
39
40 const api2 = createApi({
41 reducerPath: 'api2',
42 baseQuery: fetchBaseQuery({ baseUrl }),
43 endpoints: (builder) => ({
44 q1: builder.query({ query: () => '/success' }),
45 }),
46 })
47 return [api1, api1_2, api2] as const
48}
49
50let [api1, api1_2, api2] = createApis()
51beforeEach(() => {
52 ;[api1, api1_2, api2] = createApis()
53})
54
55const reMatchMissingMiddlewareError =
56 /Warning: Middleware for RTK-Query API at reducerPath "api" has not been added to the store/
57
58describe('missing middleware', () => {
59 test.each([
60 ['development', true],
61 ['production', false],
62 ])('%s warns if middleware is missing: %s', (env, shouldWarn) => {
63 vi.stubEnv('NODE_ENV', env)
64
65 const store = configureStore({
66 reducer: { [api1.reducerPath]: api1.reducer },
67 })
68 const doDispatch = () => {
69 store.dispatch(api1.endpoints.q1.initiate(undefined))
70 }
71 if (shouldWarn) {
72 expect(doDispatch).toThrowError(reMatchMissingMiddlewareError)
73 } else {
74 expect(doDispatch).not.toThrowError()
75 }
76 })
77
78 test('does not warn if middleware is not missing', () => {
79 const store = configureStore({
80 reducer: { [api1.reducerPath]: api1.reducer },
81 middleware: (gdm) => gdm().concat(api1.middleware),
82 })
83 store.dispatch(api1.endpoints.q1.initiate(undefined))
84
85 expect(consoleErrorSpy).not.toHaveBeenCalled()
86
87 expect(consoleWarnSpy).not.toHaveBeenCalled()
88 })
89
90 test('warns only once per api', () => {
91 const store = configureStore({
92 reducer: { [api1.reducerPath]: api1.reducer },
93 })
94 const doDispatch = () => {
95 store.dispatch(api1.endpoints.q1.initiate(undefined))
96 }
97
98 expect(doDispatch).toThrowError(reMatchMissingMiddlewareError)
99 expect(doDispatch).not.toThrowError()
100 })
101
102 test('warns multiple times for multiple apis', () => {
103 const store = configureStore({
104 reducer: {
105 [api1.reducerPath]: api1.reducer,
106 [api2.reducerPath]: api2.reducer,
107 },
108 })
109 const doDispatch1 = () => {
110 store.dispatch(api1.endpoints.q1.initiate(undefined))
111 }
112 const doDispatch2 = () => {
113 store.dispatch(api2.endpoints.q1.initiate(undefined))
114 }
115 expect(doDispatch1).toThrowError(reMatchMissingMiddlewareError)
116 expect(doDispatch2).toThrowError(
117 /Warning: Middleware for RTK-Query API at reducerPath "api2" has not been added to the store/,
118 )
119 })
120})
121
122describe('missing reducer', () => {
123 describe.each([
124 ['development', true],
125 ['production', false],
126 ])('%s warns if reducer is missing: %s', (env, shouldWarn) => {
127 beforeEach(() => {
128 vi.stubEnv('NODE_ENV', env)
129 })
130
131 afterAll(() => {
132 vi.unstubAllEnvs()
133 })
134
135 test('middleware not crashing if reducer is missing', async () => {
136 const store = configureStore({
137 reducer: { x: () => 0 },
138 // @ts-expect-error
139 middleware: (gdm) => gdm().concat(api1.middleware),
140 })
141 await store.dispatch(api1.endpoints.q1.initiate(undefined))
142
143 expect(process.env.NODE_ENV).toBe(env)
144 })
145
146 test(`warning behavior`, () => {
147 const store = configureStore({
148 reducer: { x: () => 0 },
149 // @ts-expect-error
150 middleware: (gdm) => gdm().concat(api1.middleware),
151 })
152 // @ts-expect-error
153 api1.endpoints.q1.select(undefined)(store.getState())
154
155 expect(consoleWarnSpy).not.toHaveBeenCalled()
156
157 expect(process.env.NODE_ENV).toBe(env)
158
159 if (shouldWarn) {
160 expect(consoleErrorSpy).toHaveBeenCalledOnce()
161
162 expect(consoleErrorSpy).toHaveBeenLastCalledWith(
163 'Error: No data found at `state.api`. Did you forget to add the reducer to the store?',
164 )
165 } else {
166 expect(consoleErrorSpy).not.toHaveBeenCalled()
167 }
168 })
169 })
170
171 test('does not warn if reducer is not missing', () => {
172 const store = configureStore({
173 reducer: { [api1.reducerPath]: api1.reducer },
174 middleware: (gdm) => gdm().concat(api1.middleware),
175 })
176 api1.endpoints.q1.select(undefined)(store.getState())
177
178 expect(consoleErrorSpy).not.toHaveBeenCalled()
179
180 expect(consoleWarnSpy).not.toHaveBeenCalled()
181 })
182
183 test('warns only once per api', () => {
184 const store = configureStore({
185 reducer: { x: () => 0 },
186 // @ts-expect-error
187 middleware: (gdm) => gdm().concat(api1.middleware),
188 })
189 // @ts-expect-error
190 api1.endpoints.q1.select(undefined)(store.getState())
191 // @ts-expect-error
192 api1.endpoints.q1.select(undefined)(store.getState())
193
194 expect(consoleWarnSpy).not.toHaveBeenCalled()
195
196 expect(consoleErrorSpy).toHaveBeenCalledOnce()
197
198 expect(consoleErrorSpy).toHaveBeenLastCalledWith(
199 'Error: No data found at `state.api`. Did you forget to add the reducer to the store?',
200 )
201 })
202
203 test('warns multiple times for multiple apis', () => {
204 const store = configureStore({
205 reducer: { x: () => 0 },
206 // @ts-expect-error
207 middleware: (gdm) => gdm().concat(api1.middleware),
208 })
209 // @ts-expect-error
210 api1.endpoints.q1.select(undefined)(store.getState())
211 // @ts-expect-error
212 api2.endpoints.q1.select(undefined)(store.getState())
213
214 expect(consoleWarnSpy).not.toHaveBeenCalled()
215
216 expect(consoleErrorSpy).toHaveBeenCalledTimes(2)
217
218 expect(consoleErrorSpy).toHaveBeenNthCalledWith(
219 1,
220 'Error: No data found at `state.api`. Did you forget to add the reducer to the store?',
221 )
222
223 expect(consoleErrorSpy).toHaveBeenNthCalledWith(
224 2,
225 'Error: No data found at `state.api2`. Did you forget to add the reducer to the store?',
226 )
227 })
228})
229
230test('warns for reducer and also throws error if everything is missing', async () => {
231 const store = configureStore({
232 reducer: { x: () => 0 },
233 })
234 // @ts-expect-error
235 api1.endpoints.q1.select(undefined)(store.getState())
236 const doDispatch = () => {
237 store.dispatch(api1.endpoints.q1.initiate(undefined))
238 }
239 expect(doDispatch).toThrowError(reMatchMissingMiddlewareError)
240
241 expect(consoleWarnSpy).not.toHaveBeenCalled()
242
243 expect(consoleErrorSpy).toHaveBeenCalledOnce()
244
245 expect(consoleErrorSpy).toHaveBeenLastCalledWith(
246 'Error: No data found at `state.api`. Did you forget to add the reducer to the store?',
247 )
248})
249
250describe('warns on multiple apis using the same `reducerPath`', () => {
251 test('common: two apis, same order', async () => {
252 const store = configureStore({
253 reducer: {
254 // TS 5.3 now errors on identical object keys. We want to force that behavior.
255 // @ts-ignore
256 [api1.reducerPath]: api1.reducer,
257 // @ts-ignore
258 [api1_2.reducerPath]: api1_2.reducer,
259 },
260 middleware: (gDM) => gDM().concat(api1.middleware, api1_2.middleware),
261 })
262 await store.dispatch(api1.endpoints.q1.initiate(undefined))
263
264 expect(consoleErrorSpy).not.toHaveBeenCalled()
265
266 expect(consoleWarnSpy).toHaveBeenCalledOnce()
267
268 // only second api prints
269 expect(consoleWarnSpy).toHaveBeenLastCalledWith(
270 `There is a mismatch between slice and middleware for the reducerPath "api".
271You can only have one api per reducer path, this will lead to crashes in various situations!
272If you have multiple apis, you *have* to specify the reducerPath option when using createApi!`,
273 )
274 })
275
276 test('common: two apis, opposing order', async () => {
277 const store = configureStore({
278 reducer: {
279 // @ts-ignore
280 [api1.reducerPath]: api1.reducer,
281 // @ts-ignore
282 [api1_2.reducerPath]: api1_2.reducer,
283 },
284 middleware: (gDM) => gDM().concat(api1_2.middleware, api1.middleware),
285 })
286 await store.dispatch(api1.endpoints.q1.initiate(undefined))
287
288 expect(consoleErrorSpy).not.toHaveBeenCalled()
289
290 expect(consoleWarnSpy).toHaveBeenCalledTimes(2)
291
292 // both apis print
293 expect(consoleWarnSpy).toHaveBeenNthCalledWith(
294 1,
295 `There is a mismatch between slice and middleware for the reducerPath "api".
296You can only have one api per reducer path, this will lead to crashes in various situations!
297If you have multiple apis, you *have* to specify the reducerPath option when using createApi!`,
298 )
299
300 expect(consoleWarnSpy).toHaveBeenNthCalledWith(
301 2,
302 `There is a mismatch between slice and middleware for the reducerPath "api".
303You can only have one api per reducer path, this will lead to crashes in various situations!
304If you have multiple apis, you *have* to specify the reducerPath option when using createApi!`,
305 )
306 })
307
308 test('common: two apis, only first middleware', async () => {
309 const store = configureStore({
310 reducer: {
311 // @ts-ignore
312 [api1.reducerPath]: api1.reducer,
313 // @ts-ignore
314 [api1_2.reducerPath]: api1_2.reducer,
315 },
316 middleware: (gDM) => gDM().concat(api1.middleware),
317 })
318 await store.dispatch(api1.endpoints.q1.initiate(undefined))
319
320 expect(consoleErrorSpy).not.toHaveBeenCalled()
321
322 expect(consoleWarnSpy).toHaveBeenCalledOnce()
323
324 expect(consoleWarnSpy).toHaveBeenLastCalledWith(
325 `There is a mismatch between slice and middleware for the reducerPath "api".
326You can only have one api per reducer path, this will lead to crashes in various situations!
327If you have multiple apis, you *have* to specify the reducerPath option when using createApi!`,
328 )
329 })
330
331 /**
332 * This is the one edge case that we currently cannot detect:
333 * Multiple apis with the same reducer key and only the middleware of the last api is being used.
334 *
335 * It would be great to support this case as well, but for now:
336 * "It is what it is."
337 */
338 test.todo('common: two apis, only second middleware', async () => {
339 const store = configureStore({
340 reducer: {
341 // @ts-ignore
342 [api1.reducerPath]: api1.reducer,
343 // @ts-ignore
344 [api1_2.reducerPath]: api1_2.reducer,
345 },
346 middleware: (gDM) => gDM().concat(api1_2.middleware),
347 })
348 await store.dispatch(api1.endpoints.q1.initiate(undefined))
349
350 expect(consoleErrorSpy).not.toHaveBeenCalled()
351
352 expect(consoleWarnSpy).toHaveBeenCalledOnce()
353
354 expect(consoleWarnSpy).toHaveBeenLastCalledWith(
355 `There is a mismatch between slice and middleware for the reducerPath "api".
356You can only have one api per reducer path, this will lead to crashes in various situations!
357If you have multiple apis, you *have* to specify the reducerPath option when using createApi!`,
358 )
359 })
360})
361
362describe('`console.error` on unhandled errors during `initiate`', () => {
363 test('error thrown in `baseQuery`', async () => {
364 const api = createApi({
365 baseQuery(): { data: any } {
366 throw new Error('this was kinda expected')
367 },
368 endpoints: (build) => ({
369 baseQuery: build.query<any, void>({ query() {} }),
370 }),
371 })
372 const store = configureStore({
373 reducer: { [api.reducerPath]: api.reducer },
374 middleware: (gdm) => gdm().concat(api.middleware),
375 })
376 await store.dispatch(api.endpoints.baseQuery.initiate())
377
378 expect(consoleWarnSpy).not.toHaveBeenCalled()
379
380 expect(consoleErrorSpy).toHaveBeenCalledOnce()
381
382 expect(consoleErrorSpy).toHaveBeenLastCalledWith(
383 `An unhandled error occurred processing a request for the endpoint "baseQuery".
384In the case of an unhandled error, no tags will be "provided" or "invalidated".`,
385 Error('this was kinda expected'),
386 )
387 })
388
389 test('error thrown in `queryFn`', async () => {
390 const api = createApi({
391 baseQuery() {
392 return { data: {} }
393 },
394 endpoints: (build) => ({
395 queryFn: build.query<any, void>({
396 queryFn() {
397 throw new Error('this was kinda expected')
398 },
399 }),
400 }),
401 })
402 const store = configureStore({
403 reducer: { [api.reducerPath]: api.reducer },
404 middleware: (gdm) => gdm().concat(api.middleware),
405 })
406 await store.dispatch(api.endpoints.queryFn.initiate())
407
408 expect(consoleWarnSpy).not.toHaveBeenCalled()
409
410 expect(consoleErrorSpy).toHaveBeenCalledOnce()
411
412 expect(consoleErrorSpy).toHaveBeenLastCalledWith(
413 `An unhandled error occurred processing a request for the endpoint "queryFn".
414In the case of an unhandled error, no tags will be "provided" or "invalidated".`,
415 Error('this was kinda expected'),
416 )
417 })
418
419 test('error thrown in `transformResponse`', async () => {
420 const api = createApi({
421 baseQuery() {
422 return { data: {} }
423 },
424 endpoints: (build) => ({
425 transformRspn: build.query<any, void>({
426 query() {},
427 transformResponse() {
428 throw new Error('this was kinda expected')
429 },
430 }),
431 }),
432 })
433 const store = configureStore({
434 reducer: { [api.reducerPath]: api.reducer },
435 middleware: (gdm) => gdm().concat(api.middleware),
436 })
437 await store.dispatch(api.endpoints.transformRspn.initiate())
438
439 expect(consoleWarnSpy).not.toHaveBeenCalled()
440
441 expect(consoleErrorSpy).toHaveBeenCalledOnce()
442
443 expect(consoleErrorSpy).toHaveBeenLastCalledWith(
444 `An unhandled error occurred processing a request for the endpoint "transformRspn".
445In the case of an unhandled error, no tags will be "provided" or "invalidated".`,
446 Error('this was kinda expected'),
447 )
448 })
449
450 test('error thrown in `transformErrorResponse`', async () => {
451 const api = createApi({
452 baseQuery() {
453 return { error: {} }
454 },
455 endpoints: (build) => ({
456 // @ts-ignore TS doesn't like `() => never` for `tER`
457 transformErRspn: build.query<number, void>({
458 // @ts-ignore TS doesn't like `() => never` for `tER`
459 query: () => '/dummy',
460 // @ts-ignore TS doesn't like `() => never` for `tER`
461 transformErrorResponse() {
462 throw new Error('this was kinda expected')
463 },
464 }),
465 }),
466 })
467 const store = configureStore({
468 reducer: { [api.reducerPath]: api.reducer },
469 middleware: (gdm) => gdm().concat(api.middleware),
470 })
471 await store.dispatch(api.endpoints.transformErRspn.initiate())
472
473 expect(consoleWarnSpy).not.toHaveBeenCalled()
474
475 expect(consoleErrorSpy).toHaveBeenCalledOnce()
476
477 expect(consoleErrorSpy).toHaveBeenLastCalledWith(
478 `An unhandled error occurred processing a request for the endpoint "transformErRspn".
479In the case of an unhandled error, no tags will be "provided" or "invalidated".`,
480 Error('this was kinda expected'),
481 )
482 })
483
484 test('`fetchBaseQuery`: error thrown in `prepareHeaders`', async () => {
485 const api = createApi({
486 baseQuery: fetchBaseQuery({
487 baseUrl,
488 prepareHeaders() {
489 throw new Error('this was kinda expected')
490 },
491 }),
492 endpoints: (build) => ({
493 prep: build.query<any, void>({
494 query() {
495 return '/success'
496 },
497 }),
498 }),
499 })
500 const store = configureStore({
501 reducer: { [api.reducerPath]: api.reducer },
502 middleware: (gdm) => gdm().concat(api.middleware),
503 })
504 await store.dispatch(api.endpoints.prep.initiate())
505
506 expect(consoleWarnSpy).not.toHaveBeenCalled()
507
508 expect(consoleErrorSpy).toHaveBeenCalledOnce()
509
510 expect(consoleErrorSpy).toHaveBeenLastCalledWith(
511 `An unhandled error occurred processing a request for the endpoint "prep".
512In the case of an unhandled error, no tags will be "provided" or "invalidated".`,
513 Error('this was kinda expected'),
514 )
515 })
516
517 test('`fetchBaseQuery`: error thrown in `validateStatus`', async () => {
518 const api = createApi({
519 baseQuery: fetchBaseQuery({
520 baseUrl,
521 }),
522 endpoints: (build) => ({
523 val: build.query<any, void>({
524 query() {
525 return {
526 url: '/success',
527
528 validateStatus() {
529 throw new Error('this was kinda expected')
530 },
531 }
532 },
533 }),
534 }),
535 })
536 const store = configureStore({
537 reducer: { [api.reducerPath]: api.reducer },
538 middleware: (gdm) => gdm().concat(api.middleware),
539 })
540 await store.dispatch(api.endpoints.val.initiate())
541
542 expect(consoleWarnSpy).not.toHaveBeenCalled()
543
544 expect(consoleErrorSpy).toHaveBeenCalledOnce()
545
546 expect(consoleErrorSpy).toHaveBeenLastCalledWith(
547 `An unhandled error occurred processing a request for the endpoint "val".
548In the case of an unhandled error, no tags will be "provided" or "invalidated".`,
549 Error('this was kinda expected'),
550 )
551 })
552})
Note: See TracBrowser for help on using the repository browser.