nova-right-nav / node_modules /@reduxjs /toolkit /src /query /tests /errorHandling.test.tsx
Leon4gr45's picture
Upload folder using huggingface_hub (part 9)
4a288a7 verified
Raw
History Blame Contribute Delete
18.3 kB
import type { ThunkDispatch, UnknownAction } from '@reduxjs/toolkit'
import type { BaseQueryFn, BaseQueryApi } from '@reduxjs/toolkit/query/react'
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import {
act,
fireEvent,
render,
renderHook,
screen,
waitFor,
} from '@testing-library/react'
import type { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios'
import axios from 'axios'
import { HttpResponse, http } from 'msw'
import * as React from 'react'
import { useDispatch } from 'react-redux'
import { hookWaitFor, setupApiStore } from '@internal/tests/utils/helpers'
import { server } from '@internal/query/tests/mocks/server'
const baseQuery = fetchBaseQuery({ baseUrl: 'https://example.com' })
const api = createApi({
baseQuery,
endpoints(build) {
return {
query: build.query({ query: () => '/query' }),
mutation: build.mutation({
query: () => ({ url: '/mutation', method: 'POST' }),
}),
}
},
})
const storeRef = setupApiStore(api)
const failQueryOnce = http.get(
'/query',
() => HttpResponse.json({ value: 'failed' }, { status: 500 }),
{ once: true },
)
describe('fetchBaseQuery', () => {
let commonBaseQueryApiArgs: BaseQueryApi = {} as any
beforeEach(() => {
const abortController = new AbortController()
commonBaseQueryApiArgs = {
signal: abortController.signal,
abort: (reason) =>
//@ts-ignore
abortController.abort(reason),
dispatch: storeRef.store.dispatch,
getState: storeRef.store.getState,
extra: undefined,
type: 'query',
endpoint: 'doesntmatterhere',
}
})
test('success', async () => {
await expect(
baseQuery('/success', commonBaseQueryApiArgs, {}),
).resolves.toEqual({
data: { value: 'success' },
meta: {
request: expect.any(Object),
response: expect.any(Object),
},
})
})
test('error', async () => {
server.use(failQueryOnce)
await expect(
baseQuery('/error', commonBaseQueryApiArgs, {}),
).resolves.toEqual({
error: {
data: { value: 'error' },
status: 500,
},
meta: {
request: expect.any(Object),
response: expect.any(Object),
},
})
})
})
describe('query error handling', () => {
test('success', async () => {
server.use(
http.get('https://example.com/query', () =>
HttpResponse.json({ value: 'success' }),
),
)
const { result } = renderHook(() => api.endpoints.query.useQuery({}), {
wrapper: storeRef.wrapper,
})
await hookWaitFor(() => expect(result.current.isFetching).toBeFalsy())
expect(result.current).toEqual(
expect.objectContaining({
isLoading: false,
isError: false,
isSuccess: true,
data: { value: 'success' },
}),
)
})
test('error', async () => {
server.use(
http.get('https://example.com/query', () =>
HttpResponse.json({ value: 'error' }, { status: 500 }),
),
)
const { result } = renderHook(() => api.endpoints.query.useQuery({}), {
wrapper: storeRef.wrapper,
})
await hookWaitFor(() => expect(result.current.isFetching).toBeFalsy())
expect(result.current).toEqual(
expect.objectContaining({
isLoading: false,
isError: true,
isSuccess: false,
error: {
status: 500,
data: { value: 'error' },
},
}),
)
})
test('success -> error', async () => {
server.use(
http.get('https://example.com/query', () =>
HttpResponse.json({ value: 'success' }),
),
)
const { result } = renderHook(() => api.endpoints.query.useQuery({}), {
wrapper: storeRef.wrapper,
})
await hookWaitFor(() => expect(result.current.isFetching).toBeFalsy())
expect(result.current).toEqual(
expect.objectContaining({
isLoading: false,
isError: false,
isSuccess: true,
data: { value: 'success' },
}),
)
server.use(
http.get(
'https://example.com/query',
() => HttpResponse.json({ value: 'error' }, { status: 500 }),
{ once: true },
),
)
act(() => void result.current.refetch())
await hookWaitFor(() => expect(result.current.isFetching).toBeFalsy())
expect(result.current).toEqual(
expect.objectContaining({
isLoading: false,
isError: true,
isSuccess: false,
error: {
status: 500,
data: { value: 'error' },
},
// last data will stay available
data: { value: 'success' },
}),
)
})
test('error -> success', async () => {
server.use(
http.get('https://example.com/query', () =>
HttpResponse.json({ value: 'success' }),
),
)
server.use(
http.get(
'https://example.com/query',
() => HttpResponse.json({ value: 'error' }, { status: 500 }),
{ once: true },
),
)
const { result } = renderHook(() => api.endpoints.query.useQuery({}), {
wrapper: storeRef.wrapper,
})
await hookWaitFor(() => expect(result.current.isFetching).toBeFalsy())
expect(result.current).toEqual(
expect.objectContaining({
isLoading: false,
isError: true,
isSuccess: false,
error: {
status: 500,
data: { value: 'error' },
},
}),
)
act(() => void result.current.refetch())
await hookWaitFor(() => expect(result.current.isFetching).toBeFalsy())
expect(result.current).toEqual(
expect.objectContaining({
isLoading: false,
isError: false,
isSuccess: true,
data: { value: 'success' },
}),
)
})
})
describe('mutation error handling', () => {
test('success', async () => {
server.use(
http.post('https://example.com/mutation', () =>
HttpResponse.json({ value: 'success' }),
),
)
const { result } = renderHook(() => api.endpoints.mutation.useMutation(), {
wrapper: storeRef.wrapper,
})
const [trigger] = result.current
act(() => void trigger({}))
await hookWaitFor(() => expect(result.current[1].isLoading).toBeFalsy())
expect(result.current[1]).toEqual(
expect.objectContaining({
isLoading: false,
isError: false,
isSuccess: true,
data: { value: 'success' },
}),
)
})
test('error', async () => {
server.use(
http.post('https://example.com/mutation', () =>
HttpResponse.json({ value: 'error' }, { status: 500 }),
),
)
const { result } = renderHook(() => api.endpoints.mutation.useMutation(), {
wrapper: storeRef.wrapper,
})
const [trigger] = result.current
act(() => void trigger({}))
await hookWaitFor(() => expect(result.current[1].isLoading).toBeFalsy())
expect(result.current[1]).toEqual(
expect.objectContaining({
isLoading: false,
isError: true,
isSuccess: false,
error: {
status: 500,
data: { value: 'error' },
},
}),
)
})
test('success -> error', async () => {
server.use(
http.post('https://example.com/mutation', () =>
HttpResponse.json({ value: 'success' }),
),
)
const { result } = renderHook(() => api.endpoints.mutation.useMutation(), {
wrapper: storeRef.wrapper,
})
{
const [trigger] = result.current
act(() => void trigger({}))
await hookWaitFor(() => expect(result.current[1].isLoading).toBeFalsy())
expect(result.current[1]).toEqual(
expect.objectContaining({
isLoading: false,
isError: false,
isSuccess: true,
data: { value: 'success' },
}),
)
}
server.use(
http.post(
'https://example.com/mutation',
() => HttpResponse.json({ value: 'error' }, { status: 500 }),
{ once: true },
),
)
{
const [trigger] = result.current
act(() => void trigger({}))
await hookWaitFor(() => expect(result.current[1].isLoading).toBeFalsy())
expect(result.current[1]).toEqual(
expect.objectContaining({
isLoading: false,
isError: true,
isSuccess: false,
error: {
status: 500,
data: { value: 'error' },
},
}),
)
expect(result.current[1].data).toBeUndefined()
}
})
test('error -> success', async () => {
server.use(
http.post('https://example.com/mutation', () =>
HttpResponse.json({ value: 'success' }),
),
)
server.use(
http.post(
'https://example.com/mutation',
() => HttpResponse.json({ value: 'error' }, { status: 500 }),
{ once: true },
),
)
const { result } = renderHook(() => api.endpoints.mutation.useMutation(), {
wrapper: storeRef.wrapper,
})
{
const [trigger] = result.current
act(() => void trigger({}))
await hookWaitFor(() => expect(result.current[1].isLoading).toBeFalsy())
expect(result.current[1]).toEqual(
expect.objectContaining({
isLoading: false,
isError: true,
isSuccess: false,
error: {
status: 500,
data: { value: 'error' },
},
}),
)
}
{
const [trigger] = result.current
act(() => void trigger({}))
await hookWaitFor(() => expect(result.current[1].isLoading).toBeFalsy())
expect(result.current[1]).toEqual(
expect.objectContaining({
isLoading: false,
isError: false,
isSuccess: true,
}),
)
expect(result.current[1].error).toBeUndefined()
}
})
})
describe('custom axios baseQuery', () => {
const axiosBaseQuery =
(
{ baseUrl }: { baseUrl: string } = { baseUrl: '' },
): BaseQueryFn<
{
url: string
method?: AxiosRequestConfig['method']
data?: AxiosRequestConfig['data']
},
unknown,
unknown,
unknown,
{ response: AxiosResponse; request: AxiosRequestConfig }
> =>
async ({ url, method, data }) => {
const config = { url: baseUrl + url, method, data }
try {
const result = await axios(config)
return {
data: result.data,
meta: { request: config, response: result },
}
} catch (axiosError) {
const err = axiosError as AxiosError
return {
error: {
status: err.response?.status,
data: err.response?.data,
},
meta: { request: config, response: err.response as AxiosResponse },
}
}
}
type SuccessResponse = { value: 'success' }
const api = createApi({
baseQuery: axiosBaseQuery({
baseUrl: 'https://example.com',
}),
endpoints(build) {
return {
query: build.query<SuccessResponse, void>({
query: () => ({ url: '/success', method: 'get' }),
transformResponse: (result: SuccessResponse, meta) => {
return { ...result, metaResponseData: meta?.response.data }
},
}),
mutation: build.mutation<SuccessResponse, any>({
query: () => ({ url: '/success', method: 'post' }),
}),
}
},
})
const storeRef = setupApiStore(api)
test('axiosBaseQuery transformResponse uses its custom meta format', async () => {
const result = await storeRef.store.dispatch(api.endpoints.query.initiate())
expect(result.data).toEqual({
value: 'success',
metaResponseData: { value: 'success' },
})
})
test('axios errors behave as expected', async () => {
server.use(
http.get('https://example.com/success', () =>
HttpResponse.json({ value: 'error' }, { status: 500 }),
),
)
const { result } = renderHook(() => api.endpoints.query.useQuery(), {
wrapper: storeRef.wrapper,
})
await hookWaitFor(() => expect(result.current.isFetching).toBeFalsy())
expect(result.current).toEqual(
expect.objectContaining({
isLoading: false,
isError: true,
isSuccess: false,
error: { status: 500, data: { value: 'error' } },
}),
)
})
})
describe('error handling in a component', () => {
const mockErrorResponse = { value: 'error', very: 'mean' }
const mockSuccessResponse = { value: 'success' }
const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: 'https://example.com' }),
endpoints: (build) => ({
update: build.mutation<typeof mockSuccessResponse, any>({
query: () => ({ url: 'success' }),
}),
failedUpdate: build.mutation<typeof mockSuccessResponse, any>({
query: () => ({ url: 'error' }),
}),
}),
})
const storeRef = setupApiStore(api)
test('a mutation is unwrappable and has the correct types', async () => {
server.use(
http.get(
'https://example.com/success',
() => HttpResponse.json(mockErrorResponse, { status: 500 }),
{ once: true },
),
)
function User() {
const [manualError, setManualError] = React.useState<any>()
const [update, { isLoading, data, error }] =
api.endpoints.update.useMutation()
return (
<div>
<div data-testid="isLoading">{String(isLoading)}</div>
<div data-testid="data">{JSON.stringify(data)}</div>
<div data-testid="error">{JSON.stringify(error)}</div>
<div data-testid="manuallySetError">
{JSON.stringify(manualError)}
</div>
<button
onClick={() => {
update({ name: 'hello' })
.unwrap()
.then((result) => {
setManualError(undefined)
})
.catch((error) => act(() => setManualError(error)))
}}
>
Update User
</button>
</div>
)
}
render(<User />, { wrapper: storeRef.wrapper })
await waitFor(() =>
expect(screen.getByTestId('isLoading').textContent).toBe('false'),
)
fireEvent.click(screen.getByText('Update User'))
expect(screen.getByTestId('isLoading').textContent).toBe('true')
await waitFor(() =>
expect(screen.getByTestId('isLoading').textContent).toBe('false'),
)
// Make sure the hook and the unwrapped action return the same things in an error state
await waitFor(() =>
expect(screen.getByTestId('error').textContent).toEqual(
screen.getByTestId('manuallySetError').textContent,
),
)
fireEvent.click(screen.getByText('Update User'))
expect(screen.getByTestId('isLoading').textContent).toBe('true')
await waitFor(() =>
expect(screen.getByTestId('isLoading').textContent).toBe('false'),
)
await waitFor(() =>
expect(screen.getByTestId('error').textContent).toBeFalsy(),
)
await waitFor(() =>
expect(screen.getByTestId('manuallySetError').textContent).toBeFalsy(),
)
await waitFor(() =>
expect(screen.getByTestId('data').textContent).toEqual(
JSON.stringify(mockSuccessResponse),
),
)
})
for (const track of [true, false]) {
test(`an un-subscribed mutation will still return something useful (success case, track: ${track})`, async () => {
const hook = renderHook(useDispatch, { wrapper: storeRef.wrapper })
const dispatch = hook.result.current as ThunkDispatch<
any,
any,
UnknownAction
>
let mutationqueryFulfilled: ReturnType<
ReturnType<typeof api.endpoints.update.initiate>
>
act(() => {
mutationqueryFulfilled = dispatch(
api.endpoints.update.initiate({}, { track }),
)
})
const result = await mutationqueryFulfilled!
expect(result).toMatchObject({
data: { value: 'success' },
})
})
test(`an un-subscribed mutation will still return something useful (error case, track: ${track})`, async () => {
const hook = renderHook(useDispatch, { wrapper: storeRef.wrapper })
const dispatch = hook.result.current as ThunkDispatch<
any,
any,
UnknownAction
>
let mutationqueryFulfilled: ReturnType<
ReturnType<typeof api.endpoints.failedUpdate.initiate>
>
act(() => {
mutationqueryFulfilled = dispatch(
api.endpoints.failedUpdate.initiate({}, { track }),
)
})
const result = await mutationqueryFulfilled!
expect(result).toMatchObject({
error: {
status: 500,
data: { value: 'error' },
},
})
})
test(`an un-subscribed mutation will still be unwrappable (success case), track: ${track}`, async () => {
const hook = renderHook(useDispatch, { wrapper: storeRef.wrapper })
const dispatch = hook.result.current as ThunkDispatch<
any,
any,
UnknownAction
>
let mutationqueryFulfilled: ReturnType<
ReturnType<typeof api.endpoints.update.initiate>
>
act(() => {
mutationqueryFulfilled = dispatch(
api.endpoints.update.initiate({}, { track }),
)
})
const result = await mutationqueryFulfilled!.unwrap()
expect(result).toMatchObject({
value: 'success',
})
})
test(`an un-subscribed mutation will still be unwrappable (error case, track: ${track})`, async () => {
const hook = renderHook(useDispatch, { wrapper: storeRef.wrapper })
const dispatch = hook.result.current as ThunkDispatch<
any,
any,
UnknownAction
>
let mutationqueryFulfilled: ReturnType<
ReturnType<typeof api.endpoints.failedUpdate.initiate>
>
act(() => {
mutationqueryFulfilled = dispatch(
api.endpoints.failedUpdate.initiate({}, { track }),
)
})
const unwrappedPromise = mutationqueryFulfilled!.unwrap()
await expect(unwrappedPromise).rejects.toMatchObject({
status: 500,
data: { value: 'error' },
})
})
}
})