source: node_modules/@reduxjs/toolkit/src/query/tests/queryLifecycle.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: 14.7 KB
Line 
1import { server } from '@internal/query/tests/mocks/server'
2import { setupApiStore } from '@internal/tests/utils/helpers'
3import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
4import { waitFor } from '@testing-library/react'
5import { HttpResponse, http } from 'msw'
6import { vi } from 'vitest'
7
8const api = createApi({
9 baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com' }),
10 endpoints: () => ({}),
11})
12const storeRef = setupApiStore(api)
13
14const onStart = vi.fn()
15const onSuccess = vi.fn()
16const onError = vi.fn()
17
18beforeEach(() => {
19 onStart.mockClear()
20 onSuccess.mockClear()
21 onError.mockClear()
22})
23
24describe.each([['query'], ['mutation']] as const)(
25 'generic cases: %s',
26 (type) => {
27 test(`${type}: onStart only`, async () => {
28 const extended = api.injectEndpoints({
29 overrideExisting: true,
30 endpoints: (build) => ({
31 injected: build[type as 'mutation']<unknown, string>({
32 query: () => '/success',
33 onQueryStarted(arg) {
34 onStart(arg)
35 },
36 }),
37 }),
38 })
39 storeRef.store.dispatch(extended.endpoints.injected.initiate('arg'))
40 expect(onStart).toHaveBeenCalledWith('arg')
41 })
42
43 test(`${type}: onStart and onSuccess`, async () => {
44 const extended = api.injectEndpoints({
45 overrideExisting: true,
46 endpoints: (build) => ({
47 injected: build[type as 'mutation']<number, string>({
48 query: () => '/success',
49 async onQueryStarted(arg, { queryFulfilled }) {
50 onStart(arg)
51 // awaiting without catching like this would result in an `unhandledRejection` exception if there was an error
52 // unfortunately we cannot test for that in jest.
53 const result = await queryFulfilled
54 onSuccess(result)
55 },
56 }),
57 }),
58 })
59 storeRef.store.dispatch(extended.endpoints.injected.initiate('arg'))
60 expect(onStart).toHaveBeenCalledWith('arg')
61 await waitFor(() => {
62 expect(onSuccess).toHaveBeenCalledWith({
63 data: { value: 'success' },
64 meta: {
65 request: expect.any(Request),
66 response: expect.any(Object), // Response is not available in jest env
67 },
68 })
69 })
70 })
71
72 test(`${type}: onStart and onError`, async () => {
73 const extended = api.injectEndpoints({
74 overrideExisting: true,
75 endpoints: (build) => ({
76 injected: build[type as 'mutation']<unknown, string>({
77 query: () => '/error',
78 async onQueryStarted(arg, { queryFulfilled }) {
79 onStart(arg)
80 try {
81 const result = await queryFulfilled
82 onSuccess(result)
83 } catch (e) {
84 onError(e)
85 }
86 },
87 }),
88 }),
89 })
90 storeRef.store.dispatch(extended.endpoints.injected.initiate('arg'))
91 expect(onStart).toHaveBeenCalledWith('arg')
92 await waitFor(() => {
93 expect(onError).toHaveBeenCalledWith({
94 error: {
95 status: 500,
96 data: { value: 'error' },
97 },
98 isUnhandledError: false,
99 meta: {
100 request: expect.any(Request),
101 response: expect.any(Object), // Response is not available in jest env
102 },
103 })
104 })
105 expect(onSuccess).not.toHaveBeenCalled()
106 })
107 },
108)
109
110test('query: getCacheEntry (success)', async () => {
111 const snapshot = vi.fn()
112 const extended = api.injectEndpoints({
113 overrideExisting: true,
114 endpoints: (build) => ({
115 injected: build.query<unknown, string>({
116 query: () => '/success',
117 async onQueryStarted(
118 arg,
119 { dispatch, getState, getCacheEntry, queryFulfilled },
120 ) {
121 try {
122 snapshot(getCacheEntry())
123 const result = await queryFulfilled
124 onSuccess(result)
125 snapshot(getCacheEntry())
126 } catch (e) {
127 onError(e)
128 snapshot(getCacheEntry())
129 }
130 },
131 }),
132 }),
133 })
134 const promise = storeRef.store.dispatch(
135 extended.endpoints.injected.initiate('arg'),
136 )
137
138 await waitFor(() => {
139 expect(onSuccess).toHaveBeenCalled()
140 })
141
142 expect(snapshot).toHaveBeenCalledTimes(2)
143 expect(snapshot.mock.calls[0][0]).toMatchObject({
144 endpointName: 'injected',
145 isError: false,
146 isLoading: true,
147 isSuccess: false,
148 isUninitialized: false,
149 originalArgs: 'arg',
150 requestId: promise.requestId,
151 startedTimeStamp: expect.any(Number),
152 status: 'pending',
153 })
154 expect(snapshot.mock.calls[1][0]).toMatchObject({
155 data: {
156 value: 'success',
157 },
158 endpointName: 'injected',
159 fulfilledTimeStamp: expect.any(Number),
160 isError: false,
161 isLoading: false,
162 isSuccess: true,
163 isUninitialized: false,
164 originalArgs: 'arg',
165 requestId: promise.requestId,
166 startedTimeStamp: expect.any(Number),
167 status: 'fulfilled',
168 })
169})
170
171test('query: getCacheEntry (error)', async () => {
172 const snapshot = vi.fn()
173 const extended = api.injectEndpoints({
174 overrideExisting: true,
175 endpoints: (build) => ({
176 injected: build.query<unknown, string>({
177 query: () => '/error',
178 async onQueryStarted(
179 arg,
180 { dispatch, getState, getCacheEntry, queryFulfilled },
181 ) {
182 try {
183 snapshot(getCacheEntry())
184 const result = await queryFulfilled
185 onSuccess(result)
186 snapshot(getCacheEntry())
187 } catch (e) {
188 onError(e)
189 snapshot(getCacheEntry())
190 }
191 },
192 }),
193 }),
194 })
195 const promise = storeRef.store.dispatch(
196 extended.endpoints.injected.initiate('arg'),
197 )
198
199 await waitFor(() => {
200 expect(onError).toHaveBeenCalled()
201 })
202
203 expect(snapshot.mock.calls[0][0]).toMatchObject({
204 endpointName: 'injected',
205 isError: false,
206 isLoading: true,
207 isSuccess: false,
208 isUninitialized: false,
209 originalArgs: 'arg',
210 requestId: promise.requestId,
211 startedTimeStamp: expect.any(Number),
212 status: 'pending',
213 })
214 expect(snapshot.mock.calls[1][0]).toMatchObject({
215 error: {
216 data: { value: 'error' },
217 status: 500,
218 },
219 endpointName: 'injected',
220 isError: true,
221 isLoading: false,
222 isSuccess: false,
223 isUninitialized: false,
224 originalArgs: 'arg',
225 requestId: promise.requestId,
226 startedTimeStamp: expect.any(Number),
227 status: 'rejected',
228 })
229})
230
231test('mutation: getCacheEntry (success)', async () => {
232 const snapshot = vi.fn()
233 const extended = api.injectEndpoints({
234 overrideExisting: true,
235 endpoints: (build) => ({
236 injected: build.mutation<unknown, string>({
237 query: () => '/success',
238 async onQueryStarted(
239 arg,
240 { dispatch, getState, getCacheEntry, queryFulfilled },
241 ) {
242 try {
243 snapshot(getCacheEntry())
244 const result = await queryFulfilled
245 onSuccess(result)
246 snapshot(getCacheEntry())
247 } catch (e) {
248 onError(e)
249 snapshot(getCacheEntry())
250 }
251 },
252 }),
253 }),
254 })
255 const promise = storeRef.store.dispatch(
256 extended.endpoints.injected.initiate('arg'),
257 )
258
259 await waitFor(() => {
260 expect(onSuccess).toHaveBeenCalled()
261 })
262
263 expect(snapshot).toHaveBeenCalledTimes(2)
264 expect(snapshot.mock.calls[0][0]).toMatchObject({
265 endpointName: 'injected',
266 isError: false,
267 isLoading: true,
268 isSuccess: false,
269 isUninitialized: false,
270 startedTimeStamp: expect.any(Number),
271 status: 'pending',
272 })
273 expect(snapshot.mock.calls[1][0]).toMatchObject({
274 data: {
275 value: 'success',
276 },
277 endpointName: 'injected',
278 fulfilledTimeStamp: expect.any(Number),
279 isError: false,
280 isLoading: false,
281 isSuccess: true,
282 isUninitialized: false,
283 startedTimeStamp: expect.any(Number),
284 status: 'fulfilled',
285 })
286})
287
288test('mutation: getCacheEntry (error)', async () => {
289 const snapshot = vi.fn()
290 const extended = api.injectEndpoints({
291 overrideExisting: true,
292 endpoints: (build) => ({
293 injected: build.mutation<unknown, string>({
294 query: () => '/error',
295 async onQueryStarted(
296 arg,
297 { dispatch, getState, getCacheEntry, queryFulfilled },
298 ) {
299 try {
300 snapshot(getCacheEntry())
301 const result = await queryFulfilled
302 onSuccess(result)
303 snapshot(getCacheEntry())
304 } catch (e) {
305 onError(e)
306 snapshot(getCacheEntry())
307 }
308 },
309 }),
310 }),
311 })
312 const promise = storeRef.store.dispatch(
313 extended.endpoints.injected.initiate('arg'),
314 )
315
316 await waitFor(() => {
317 expect(onError).toHaveBeenCalled()
318 })
319
320 expect(snapshot.mock.calls[0][0]).toMatchObject({
321 endpointName: 'injected',
322 isError: false,
323 isLoading: true,
324 isSuccess: false,
325 isUninitialized: false,
326 startedTimeStamp: expect.any(Number),
327 status: 'pending',
328 })
329 expect(snapshot.mock.calls[1][0]).toMatchObject({
330 error: {
331 data: { value: 'error' },
332 status: 500,
333 },
334 endpointName: 'injected',
335 isError: true,
336 isLoading: false,
337 isSuccess: false,
338 isUninitialized: false,
339 startedTimeStamp: expect.any(Number),
340 status: 'rejected',
341 })
342})
343
344test('query: updateCachedData', async () => {
345 const extended = api.injectEndpoints({
346 overrideExisting: true,
347 endpoints: (build) => ({
348 injected: build.query<{ value: string }, string>({
349 query: () => '/success',
350 async onQueryStarted(
351 arg,
352 {
353 dispatch,
354 getState,
355 getCacheEntry,
356 updateCachedData,
357 queryFulfilled,
358 },
359 ) {
360 // calling `updateCachedData` when there is no data yet should not do anything
361 // but if there is a cache value it will be updated & overwritten by the next successful result
362 updateCachedData((draft) => {
363 draft.value += '.'
364 })
365
366 try {
367 await queryFulfilled
368 onSuccess(getCacheEntry().data)
369 } catch (error) {
370 updateCachedData((draft) => {
371 draft.value += 'x'
372 })
373 onError(getCacheEntry().data)
374 }
375 },
376 }),
377 }),
378 })
379
380 // request 1: success
381 expect(onSuccess).not.toHaveBeenCalled()
382 storeRef.store.dispatch(extended.endpoints.injected.initiate('arg'))
383
384 await waitFor(() => {
385 expect(onSuccess).toHaveBeenCalled()
386 })
387 expect(onSuccess).toHaveBeenCalledWith({ value: 'success' })
388 onSuccess.mockClear()
389
390 // request 2: error
391 expect(onError).not.toHaveBeenCalled()
392 server.use(
393 http.get(
394 'https://example.com/success',
395 () => {
396 return HttpResponse.json({ value: 'failed' }, { status: 500 })
397 },
398 { once: true },
399 ),
400 )
401 storeRef.store.dispatch(
402 extended.endpoints.injected.initiate('arg', { forceRefetch: true }),
403 )
404
405 await waitFor(() => {
406 expect(onError).toHaveBeenCalled()
407 })
408 expect(onError).toHaveBeenCalledWith({ value: 'success.x' })
409
410 // request 3: success
411 expect(onSuccess).not.toHaveBeenCalled()
412
413 storeRef.store.dispatch(
414 extended.endpoints.injected.initiate('arg', { forceRefetch: true }),
415 )
416
417 await waitFor(() => {
418 expect(onSuccess).toHaveBeenCalled()
419 })
420 expect(onSuccess).toHaveBeenCalledWith({ value: 'success' })
421 onSuccess.mockClear()
422})
423
424test('infinite query: updateCachedData', async () => {
425 const extended = api.injectEndpoints({
426 overrideExisting: true,
427 endpoints: (build) => ({
428 infiniteInjected: build.infiniteQuery<{ value: string }, string, number>({
429 query: () => '/success',
430 infiniteQueryOptions: {
431 initialPageParam: 1,
432 getNextPageParam: (
433 lastPage,
434 allPages,
435 lastPageParam,
436 allPageParams,
437 ) => lastPageParam + 1,
438 },
439 async onQueryStarted(
440 arg,
441 {
442 dispatch,
443 getState,
444 getCacheEntry,
445 updateCachedData,
446 queryFulfilled,
447 },
448 ) {
449 // calling `updateCachedData` when there is no data yet should not do anything
450 // but if there is a cache value it will be updated & overwritten by the next successful result
451 updateCachedData((draft) => {
452 draft.pages = [{ value: '.' }]
453 draft.pageParams = [1]
454 })
455
456 try {
457 await queryFulfilled
458 onSuccess(getCacheEntry().data)
459 } catch (error) {
460 updateCachedData((draft) => {
461 draft.pages = [{ value: 'success.x' }]
462 draft.pageParams = [1]
463 })
464 onError(getCacheEntry().data)
465 }
466 },
467 }),
468 }),
469 })
470
471 // request 1: success
472 expect(onSuccess).not.toHaveBeenCalled()
473 storeRef.store.dispatch(extended.endpoints.infiniteInjected.initiate('arg'))
474
475 await waitFor(() => {
476 expect(onSuccess).toHaveBeenCalled()
477 })
478 expect(onSuccess).toHaveBeenCalledWith({
479 pages: [{ value: 'success' }],
480 pageParams: [1],
481 })
482 onSuccess.mockClear()
483
484 // request 2: error
485 expect(onError).not.toHaveBeenCalled()
486 server.use(
487 http.get(
488 'https://example.com/success',
489 () => {
490 return HttpResponse.json({ value: 'failed' }, { status: 500 })
491 },
492 { once: true },
493 ),
494 )
495 storeRef.store.dispatch(
496 extended.endpoints.infiniteInjected.initiate('arg', { forceRefetch: true }),
497 )
498
499 await waitFor(() => {
500 expect(onError).toHaveBeenCalled()
501 })
502 expect(onError).toHaveBeenCalledWith({
503 pages: [{ value: 'success.x' }],
504 pageParams: [1],
505 })
506
507 // request 3: success
508 expect(onSuccess).not.toHaveBeenCalled()
509
510 storeRef.store.dispatch(
511 extended.endpoints.infiniteInjected.initiate('arg', { forceRefetch: true }),
512 )
513
514 await waitFor(() => {
515 expect(onSuccess).toHaveBeenCalled()
516 })
517 expect(onSuccess).toHaveBeenCalledWith({
518 pages: [{ value: 'success' }],
519 pageParams: [1],
520 })
521 onSuccess.mockClear()
522})
523
524test('query: will only start lifecycle if query is not skipped due to `condition`', async () => {
525 const extended = api.injectEndpoints({
526 overrideExisting: true,
527 endpoints: (build) => ({
528 injected: build.query<unknown, string>({
529 query: () => '/success',
530 onQueryStarted(arg) {
531 onStart(arg)
532 },
533 }),
534 }),
535 })
536 const promise = storeRef.store.dispatch(
537 extended.endpoints.injected.initiate('arg'),
538 )
539 expect(onStart).toHaveBeenCalledOnce()
540 storeRef.store.dispatch(extended.endpoints.injected.initiate('arg'))
541 expect(onStart).toHaveBeenCalledOnce()
542 await promise
543 storeRef.store.dispatch(
544 extended.endpoints.injected.initiate('arg', { forceRefetch: true }),
545 )
546 expect(onStart).toHaveBeenCalledTimes(2)
547})
Note: See TracBrowser for help on using the repository browser.