Skip to content

Commit 57316ca

Browse files
phryneasmarkerikson
authored andcommitted
correctly handle console logs in tests (#1567)
1 parent b4d940e commit 57316ca

File tree

7 files changed

+138
-29
lines changed

7 files changed

+138
-29
lines changed

packages/toolkit/jest.setup.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,5 @@ process.on('unhandledRejection', (error) => {
1414
// eslint-disable-next-line no-undef
1515
fail(error)
1616
})
17+
18+
process.env.NODE_ENV = 'development'

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { configureStore } from '@reduxjs/toolkit'
22
import { createApi, fakeBaseQuery } from '@reduxjs/toolkit/query'
3+
import './helpers'
34

45
type CustomErrorType = { type: 'Custom' }
56

@@ -122,8 +123,15 @@ const store = configureStore({
122123

123124
test('fakeBaseQuery throws when invoking query', async () => {
124125
const thunk = api.endpoints.withQuery.initiate('')
125-
const result = await store.dispatch(thunk)
126-
expect(result.error).toEqual({
126+
let result: { error?: any } | undefined
127+
await expect(async () => {
128+
result = await store.dispatch(thunk)
129+
}).toHaveConsoleOutput(
130+
`An unhandled error occured processing a request for the endpoint "withQuery".
131+
In the case of an unhandled error, no tags will be "provided" or "invalidated". [Error: When using \`fakeBaseQuery\`, all queries & mutations must use the \`queryFn\` definition syntax.]`
132+
)
133+
134+
expect(result!.error).toEqual({
127135
message:
128136
'When using `fakeBaseQuery`, all queries & mutations must use the `queryFn` definition syntax.',
129137
name: 'Error',

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -765,11 +765,12 @@ describe('FormData', () => {
765765

766766
describe('still throws on completely unexpected errors', () => {
767767
test('', async () => {
768+
const error = new Error('some unexpected error')
768769
const req = baseQuery(
769770
{
770771
url: '/success',
771772
validateStatus() {
772-
throw new Error('some unexpected error')
773+
throw error
773774
},
774775
},
775776
{
@@ -781,8 +782,6 @@ describe('still throws on completely unexpected errors', () => {
781782
{}
782783
)
783784
expect(req).toBeInstanceOf(Promise)
784-
await expect(req).rejects.toMatchInlineSnapshot(
785-
`[Error: some unexpected error]`
786-
)
785+
await expect(req).rejects.toBe(error)
787786
})
788787
})

packages/toolkit/src/query/tests/helpers.tsx

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ import type { Reducer } from 'react'
1212
import React, { useCallback } from 'react'
1313
import { Provider } from 'react-redux'
1414

15+
import {
16+
mockConsole,
17+
createConsole,
18+
getLog,
19+
} from 'console-testing-library/pure'
20+
1521
export const ANY = 0 as any
1622

1723
export const DEFAULT_DELAY_MS = 150
@@ -106,6 +112,54 @@ expect.extend({
106112
},
107113
})
108114

115+
declare global {
116+
namespace jest {
117+
interface Matchers<R> {
118+
toHaveConsoleOutput(expectedOutput: string): Promise<R>
119+
}
120+
}
121+
}
122+
123+
function normalize(str: string) {
124+
return str
125+
.normalize()
126+
.replace(/\s*\r?\n\r?\s*/g, '')
127+
.trim()
128+
}
129+
130+
expect.extend({
131+
async toHaveConsoleOutput(
132+
fn: () => void | Promise<void>,
133+
expectedOutput: string
134+
) {
135+
const restore = mockConsole(createConsole())
136+
await fn()
137+
const log = getLog().log
138+
restore()
139+
140+
if (normalize(log) === normalize(expectedOutput))
141+
return {
142+
message: () => `Console output matches
143+
===
144+
${expectedOutput}
145+
===`,
146+
pass: true,
147+
}
148+
else
149+
return {
150+
message: () => `Console output
151+
===
152+
${log}
153+
===
154+
does not match
155+
===
156+
${expectedOutput}
157+
===`,
158+
pass: false,
159+
}
160+
},
161+
})
162+
109163
export const actionsReducer = {
110164
actions: (state: AnyAction[] = [], action: AnyAction) => {
111165
return [...state, action]

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ const api = createApi({
1515
baseQuery: (...args: any[]) => {
1616
const result = baseQuery(...args)
1717
if ('then' in result)
18-
return result.then((data: any) => ({ data, meta: 'meta' }))
18+
return result
19+
.then((data: any) => ({ data, meta: 'meta' }))
20+
.catch((e: any) => ({ error: e }))
1921
return { data: result, meta: 'meta' }
2022
},
2123
tagTypes: ['Post'],
@@ -38,7 +40,7 @@ const api = createApi({
3840
)
3941
queryFulfilled.catch(undo)
4042
},
41-
invalidatesTags: ['Post'],
43+
invalidatesTags: (result) => (result ? ['Post'] : []),
4244
}),
4345
}),
4446
})
@@ -119,8 +121,9 @@ describe('basic lifecycle', () => {
119121
expect(onSuccess).not.toHaveBeenCalled()
120122
await act(() => waitMs(5))
121123
expect(onError).toHaveBeenCalledWith({
122-
error: { message: 'error' },
123-
isUnhandledError: true,
124+
error: 'error',
125+
isUnhandledError: false,
126+
meta: undefined,
124127
})
125128
expect(onSuccess).not.toHaveBeenCalled()
126129
})

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

Lines changed: 54 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
import type { SerializedError } from '@reduxjs/toolkit'
12
import { configureStore } from '@reduxjs/toolkit'
23
import type { BaseQueryFn, FetchBaseQueryError } from '@reduxjs/toolkit/query'
34
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
45
import type { Post } from './mocks/server'
56
import { posts } from './mocks/server'
67
import { actionsReducer, setupApiStore } from './helpers'
8+
import type { QuerySubState } from '@reduxjs/toolkit/dist/query/core/apiState'
79

810
describe('queryFn base implementation tests', () => {
911
const baseQuery: BaseQueryFn<string, { wrappedByBaseQuery: string }, string> =
@@ -172,7 +174,15 @@ describe('queryFn base implementation tests', () => {
172174
['withAsyncThrowingQueryFn', withAsyncThrowingQueryFn, 'throw'],
173175
])('%s1', async (endpointName, endpoint, expectedResult) => {
174176
const thunk = endpoint.initiate(endpointName)
175-
const result = await store.dispatch(thunk)
177+
let result: undefined | QuerySubState<any> = undefined
178+
await expect(async () => {
179+
result = await store.dispatch(thunk)
180+
}).toHaveConsoleOutput(
181+
endpointName.includes('Throw')
182+
? `An unhandled error occured processing a request for the endpoint "${endpointName}".
183+
In the case of an unhandled error, no tags will be "provided" or "invalidated". [Error: resultFrom(${endpointName})]`
184+
: ''
185+
)
176186
if (expectedResult === 'data') {
177187
expect(result).toEqual(
178188
expect.objectContaining({
@@ -209,7 +219,19 @@ describe('queryFn base implementation tests', () => {
209219
],
210220
])('%s', async (endpointName, endpoint, expectedResult) => {
211221
const thunk = endpoint.initiate(endpointName)
212-
const result = await store.dispatch(thunk)
222+
let result:
223+
| undefined
224+
| { data: string }
225+
| { error: string | SerializedError } = undefined
226+
await expect(async () => {
227+
result = await store.dispatch(thunk)
228+
}).toHaveConsoleOutput(
229+
endpointName.includes('Throw')
230+
? `An unhandled error occured processing a request for the endpoint "${endpointName}".
231+
In the case of an unhandled error, no tags will be "provided" or "invalidated". [Error: resultFrom(${endpointName})]`
232+
: ''
233+
)
234+
213235
if (expectedResult === 'data') {
214236
expect(result).toEqual(
215237
expect.objectContaining({
@@ -236,17 +258,32 @@ describe('queryFn base implementation tests', () => {
236258
test('neither provided', async () => {
237259
{
238260
const thunk = withNeither.initiate('withNeither')
239-
const result = await store.dispatch(thunk)
240-
expect(result.error).toEqual(
261+
let result: QuerySubState<any>
262+
await expect(async () => {
263+
result = await store.dispatch(thunk)
264+
}).toHaveConsoleOutput(
265+
`An unhandled error occured processing a request for the endpoint "withNeither".
266+
In the case of an unhandled error, no tags will be "provided" or "invalidated". [TypeError: endpointDefinition.queryFn is not a function]`
267+
)
268+
expect(result!.error).toEqual(
241269
expect.objectContaining({
242270
message: 'endpointDefinition.queryFn is not a function',
243271
})
244272
)
245273
}
246274
{
275+
let result:
276+
| undefined
277+
| { data: string }
278+
| { error: string | SerializedError } = undefined
247279
const thunk = mutationWithNeither.initiate('mutationWithNeither')
248-
const result = await store.dispatch(thunk)
249-
expect('error' in result && result.error).toEqual(
280+
await expect(async () => {
281+
result = await store.dispatch(thunk)
282+
}).toHaveConsoleOutput(
283+
`An unhandled error occured processing a request for the endpoint "mutationWithNeither".
284+
In the case of an unhandled error, no tags will be "provided" or "invalidated". [TypeError: endpointDefinition.queryFn is not a function]`
285+
)
286+
expect((result as any).error).toEqual(
250287
expect.objectContaining({
251288
message: 'endpointDefinition.queryFn is not a function',
252289
})
@@ -336,11 +373,17 @@ describe('usage scenario tests', () => {
336373
})
337374

338375
it('can wrap a service like Firebase and handle errors', async () => {
339-
const result = await storeRef.store.dispatch(
340-
api.endpoints.getMissingFirebaseUser.initiate(1)
341-
)
342-
expect(result.data).toBeUndefined()
343-
expect(result.error).toEqual(
376+
let result: QuerySubState<any>
377+
await expect(async () => {
378+
result = await storeRef.store.dispatch(
379+
api.endpoints.getMissingFirebaseUser.initiate(1)
380+
)
381+
})
382+
.toHaveConsoleOutput(`An unhandled error occured processing a request for the endpoint "getMissingFirebaseUser".
383+
In the case of an unhandled error, no tags will be "provided" or "invalidated". [Error: Missing user]`)
384+
385+
expect(result!.data).toBeUndefined()
386+
expect(result!.error).toEqual(
344387
expect.objectContaining({
345388
message: 'Missing user',
346389
name: 'Error',

packages/toolkit/src/query/tests/retry.test.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ describe('configuration', () => {
2121
ReturnType<BaseQueryFn>,
2222
Parameters<BaseQueryFn>
2323
>()
24-
baseBaseQuery.mockRejectedValue(new Error('rejected'))
24+
baseBaseQuery.mockResolvedValue({ error: 'rejected' })
2525

2626
const baseQuery = retry(baseBaseQuery)
2727
const api = createApi({
@@ -46,7 +46,7 @@ describe('configuration', () => {
4646
ReturnType<BaseQueryFn>,
4747
Parameters<BaseQueryFn>
4848
>()
49-
baseBaseQuery.mockRejectedValue(new Error('rejected'))
49+
baseBaseQuery.mockResolvedValue({ error: 'rejected' })
5050

5151
const baseQuery = retry(baseBaseQuery, { maxRetries: 3 })
5252
const api = createApi({
@@ -71,7 +71,7 @@ describe('configuration', () => {
7171
ReturnType<BaseQueryFn>,
7272
Parameters<BaseQueryFn>
7373
>()
74-
baseBaseQuery.mockRejectedValue(new Error('rejected'))
74+
baseBaseQuery.mockResolvedValue({ error: 'rejected' })
7575

7676
const baseQuery = retry(baseBaseQuery, { maxRetries: 3 })
7777
const api = createApi({
@@ -109,8 +109,8 @@ describe('configuration', () => {
109109
Parameters<BaseQueryFn>
110110
>()
111111
baseBaseQuery
112-
.mockRejectedValueOnce(new Error('rejected'))
113-
.mockRejectedValueOnce(new Error('rejected'))
112+
.mockResolvedValueOnce({ error: 'rejected' })
113+
.mockResolvedValueOnce({ error: 'rejected' })
114114
.mockResolvedValue({ data: { success: true } })
115115

116116
const baseQuery = retry(baseBaseQuery, { maxRetries: 10 })
@@ -136,7 +136,7 @@ describe('configuration', () => {
136136
ReturnType<BaseQueryFn>,
137137
Parameters<BaseQueryFn>
138138
>()
139-
baseBaseQuery.mockRejectedValue(new Error('rejected'))
139+
baseBaseQuery.mockResolvedValue({ error: 'rejected' })
140140

141141
const baseQuery = retry(baseBaseQuery, { maxRetries: 3 })
142142
const api = createApi({
@@ -263,7 +263,7 @@ describe('configuration', () => {
263263
ReturnType<BaseQueryFn>,
264264
Parameters<BaseQueryFn>
265265
>()
266-
baseBaseQuery.mockRejectedValue(new Error('rejected'))
266+
baseBaseQuery.mockResolvedValue({ error: 'rejected' })
267267

268268
const baseQuery = retry(retry(baseBaseQuery, { maxRetries: 3 }), {
269269
maxRetries: 3,
@@ -291,7 +291,7 @@ describe('configuration', () => {
291291
ReturnType<BaseQueryFn>,
292292
Parameters<BaseQueryFn>
293293
>()
294-
baseBaseQuery.mockRejectedValue(new Error('rejected'))
294+
baseBaseQuery.mockResolvedValue({ error: 'rejected' })
295295

296296
const baseQuery = retry(baseBaseQuery, {
297297
maxRetries: 8,

0 commit comments

Comments
 (0)