Skip to content

Commit

Permalink
correctly handle console logs in tests (#1567)
Browse files Browse the repository at this point in the history
  • Loading branch information
phryneas authored and markerikson committed Oct 5, 2021
1 parent b4d940e commit 57316ca
Show file tree
Hide file tree
Showing 7 changed files with 138 additions and 29 deletions.
2 changes: 2 additions & 0 deletions packages/toolkit/jest.setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ process.on('unhandledRejection', (error) => {
// eslint-disable-next-line no-undef
fail(error)
})

process.env.NODE_ENV = 'development'
12 changes: 10 additions & 2 deletions packages/toolkit/src/query/tests/fakeBaseQuery.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { configureStore } from '@reduxjs/toolkit'
import { createApi, fakeBaseQuery } from '@reduxjs/toolkit/query'
import './helpers'

type CustomErrorType = { type: 'Custom' }

Expand Down Expand Up @@ -122,8 +123,15 @@ const store = configureStore({

test('fakeBaseQuery throws when invoking query', async () => {
const thunk = api.endpoints.withQuery.initiate('')
const result = await store.dispatch(thunk)
expect(result.error).toEqual({
let result: { error?: any } | undefined
await expect(async () => {
result = await store.dispatch(thunk)
}).toHaveConsoleOutput(
`An unhandled error occured processing a request for the endpoint "withQuery".
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.]`
)

expect(result!.error).toEqual({
message:
'When using `fakeBaseQuery`, all queries & mutations must use the `queryFn` definition syntax.',
name: 'Error',
Expand Down
7 changes: 3 additions & 4 deletions packages/toolkit/src/query/tests/fetchBaseQuery.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -765,11 +765,12 @@ describe('FormData', () => {

describe('still throws on completely unexpected errors', () => {
test('', async () => {
const error = new Error('some unexpected error')
const req = baseQuery(
{
url: '/success',
validateStatus() {
throw new Error('some unexpected error')
throw error
},
},
{
Expand All @@ -781,8 +782,6 @@ describe('still throws on completely unexpected errors', () => {
{}
)
expect(req).toBeInstanceOf(Promise)
await expect(req).rejects.toMatchInlineSnapshot(
`[Error: some unexpected error]`
)
await expect(req).rejects.toBe(error)
})
})
54 changes: 54 additions & 0 deletions packages/toolkit/src/query/tests/helpers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ import type { Reducer } from 'react'
import React, { useCallback } from 'react'
import { Provider } from 'react-redux'

import {
mockConsole,
createConsole,
getLog,
} from 'console-testing-library/pure'

export const ANY = 0 as any

export const DEFAULT_DELAY_MS = 150
Expand Down Expand Up @@ -106,6 +112,54 @@ expect.extend({
},
})

declare global {
namespace jest {
interface Matchers<R> {
toHaveConsoleOutput(expectedOutput: string): Promise<R>
}
}
}

function normalize(str: string) {
return str
.normalize()
.replace(/\s*\r?\n\r?\s*/g, '')
.trim()
}

expect.extend({
async toHaveConsoleOutput(
fn: () => void | Promise<void>,
expectedOutput: string
) {
const restore = mockConsole(createConsole())
await fn()
const log = getLog().log
restore()

if (normalize(log) === normalize(expectedOutput))
return {
message: () => `Console output matches
===
${expectedOutput}
===`,
pass: true,
}
else
return {
message: () => `Console output
===
${log}
===
does not match
===
${expectedOutput}
===`,
pass: false,
}
},
})

export const actionsReducer = {
actions: (state: AnyAction[] = [], action: AnyAction) => {
return [...state, action]
Expand Down
11 changes: 7 additions & 4 deletions packages/toolkit/src/query/tests/optimisticUpdates.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ const api = createApi({
baseQuery: (...args: any[]) => {
const result = baseQuery(...args)
if ('then' in result)
return result.then((data: any) => ({ data, meta: 'meta' }))
return result
.then((data: any) => ({ data, meta: 'meta' }))
.catch((e: any) => ({ error: e }))
return { data: result, meta: 'meta' }
},
tagTypes: ['Post'],
Expand All @@ -38,7 +40,7 @@ const api = createApi({
)
queryFulfilled.catch(undo)
},
invalidatesTags: ['Post'],
invalidatesTags: (result) => (result ? ['Post'] : []),
}),
}),
})
Expand Down Expand Up @@ -119,8 +121,9 @@ describe('basic lifecycle', () => {
expect(onSuccess).not.toHaveBeenCalled()
await act(() => waitMs(5))
expect(onError).toHaveBeenCalledWith({
error: { message: 'error' },
isUnhandledError: true,
error: 'error',
isUnhandledError: false,
meta: undefined,
})
expect(onSuccess).not.toHaveBeenCalled()
})
Expand Down
65 changes: 54 additions & 11 deletions packages/toolkit/src/query/tests/queryFn.test.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import type { SerializedError } from '@reduxjs/toolkit'
import { configureStore } from '@reduxjs/toolkit'
import type { BaseQueryFn, FetchBaseQueryError } from '@reduxjs/toolkit/query'
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
import type { Post } from './mocks/server'
import { posts } from './mocks/server'
import { actionsReducer, setupApiStore } from './helpers'
import type { QuerySubState } from '@reduxjs/toolkit/dist/query/core/apiState'

describe('queryFn base implementation tests', () => {
const baseQuery: BaseQueryFn<string, { wrappedByBaseQuery: string }, string> =
Expand Down Expand Up @@ -172,7 +174,15 @@ describe('queryFn base implementation tests', () => {
['withAsyncThrowingQueryFn', withAsyncThrowingQueryFn, 'throw'],
])('%s1', async (endpointName, endpoint, expectedResult) => {
const thunk = endpoint.initiate(endpointName)
const result = await store.dispatch(thunk)
let result: undefined | QuerySubState<any> = undefined
await expect(async () => {
result = await store.dispatch(thunk)
}).toHaveConsoleOutput(
endpointName.includes('Throw')
? `An unhandled error occured processing a request for the endpoint "${endpointName}".
In the case of an unhandled error, no tags will be "provided" or "invalidated". [Error: resultFrom(${endpointName})]`
: ''
)
if (expectedResult === 'data') {
expect(result).toEqual(
expect.objectContaining({
Expand Down Expand Up @@ -209,7 +219,19 @@ describe('queryFn base implementation tests', () => {
],
])('%s', async (endpointName, endpoint, expectedResult) => {
const thunk = endpoint.initiate(endpointName)
const result = await store.dispatch(thunk)
let result:
| undefined
| { data: string }
| { error: string | SerializedError } = undefined
await expect(async () => {
result = await store.dispatch(thunk)
}).toHaveConsoleOutput(
endpointName.includes('Throw')
? `An unhandled error occured processing a request for the endpoint "${endpointName}".
In the case of an unhandled error, no tags will be "provided" or "invalidated". [Error: resultFrom(${endpointName})]`
: ''
)

if (expectedResult === 'data') {
expect(result).toEqual(
expect.objectContaining({
Expand All @@ -236,17 +258,32 @@ describe('queryFn base implementation tests', () => {
test('neither provided', async () => {
{
const thunk = withNeither.initiate('withNeither')
const result = await store.dispatch(thunk)
expect(result.error).toEqual(
let result: QuerySubState<any>
await expect(async () => {
result = await store.dispatch(thunk)
}).toHaveConsoleOutput(
`An unhandled error occured processing a request for the endpoint "withNeither".
In the case of an unhandled error, no tags will be "provided" or "invalidated". [TypeError: endpointDefinition.queryFn is not a function]`
)
expect(result!.error).toEqual(
expect.objectContaining({
message: 'endpointDefinition.queryFn is not a function',
})
)
}
{
let result:
| undefined
| { data: string }
| { error: string | SerializedError } = undefined
const thunk = mutationWithNeither.initiate('mutationWithNeither')
const result = await store.dispatch(thunk)
expect('error' in result && result.error).toEqual(
await expect(async () => {
result = await store.dispatch(thunk)
}).toHaveConsoleOutput(
`An unhandled error occured processing a request for the endpoint "mutationWithNeither".
In the case of an unhandled error, no tags will be "provided" or "invalidated". [TypeError: endpointDefinition.queryFn is not a function]`
)
expect((result as any).error).toEqual(
expect.objectContaining({
message: 'endpointDefinition.queryFn is not a function',
})
Expand Down Expand Up @@ -336,11 +373,17 @@ describe('usage scenario tests', () => {
})

it('can wrap a service like Firebase and handle errors', async () => {
const result = await storeRef.store.dispatch(
api.endpoints.getMissingFirebaseUser.initiate(1)
)
expect(result.data).toBeUndefined()
expect(result.error).toEqual(
let result: QuerySubState<any>
await expect(async () => {
result = await storeRef.store.dispatch(
api.endpoints.getMissingFirebaseUser.initiate(1)
)
})
.toHaveConsoleOutput(`An unhandled error occured processing a request for the endpoint "getMissingFirebaseUser".
In the case of an unhandled error, no tags will be "provided" or "invalidated". [Error: Missing user]`)

expect(result!.data).toBeUndefined()
expect(result!.error).toEqual(
expect.objectContaining({
message: 'Missing user',
name: 'Error',
Expand Down
16 changes: 8 additions & 8 deletions packages/toolkit/src/query/tests/retry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ describe('configuration', () => {
ReturnType<BaseQueryFn>,
Parameters<BaseQueryFn>
>()
baseBaseQuery.mockRejectedValue(new Error('rejected'))
baseBaseQuery.mockResolvedValue({ error: 'rejected' })

const baseQuery = retry(baseBaseQuery)
const api = createApi({
Expand All @@ -46,7 +46,7 @@ describe('configuration', () => {
ReturnType<BaseQueryFn>,
Parameters<BaseQueryFn>
>()
baseBaseQuery.mockRejectedValue(new Error('rejected'))
baseBaseQuery.mockResolvedValue({ error: 'rejected' })

const baseQuery = retry(baseBaseQuery, { maxRetries: 3 })
const api = createApi({
Expand All @@ -71,7 +71,7 @@ describe('configuration', () => {
ReturnType<BaseQueryFn>,
Parameters<BaseQueryFn>
>()
baseBaseQuery.mockRejectedValue(new Error('rejected'))
baseBaseQuery.mockResolvedValue({ error: 'rejected' })

const baseQuery = retry(baseBaseQuery, { maxRetries: 3 })
const api = createApi({
Expand Down Expand Up @@ -109,8 +109,8 @@ describe('configuration', () => {
Parameters<BaseQueryFn>
>()
baseBaseQuery
.mockRejectedValueOnce(new Error('rejected'))
.mockRejectedValueOnce(new Error('rejected'))
.mockResolvedValueOnce({ error: 'rejected' })
.mockResolvedValueOnce({ error: 'rejected' })
.mockResolvedValue({ data: { success: true } })

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

const baseQuery = retry(baseBaseQuery, { maxRetries: 3 })
const api = createApi({
Expand Down Expand Up @@ -263,7 +263,7 @@ describe('configuration', () => {
ReturnType<BaseQueryFn>,
Parameters<BaseQueryFn>
>()
baseBaseQuery.mockRejectedValue(new Error('rejected'))
baseBaseQuery.mockResolvedValue({ error: 'rejected' })

const baseQuery = retry(retry(baseBaseQuery, { maxRetries: 3 }), {
maxRetries: 3,
Expand Down Expand Up @@ -291,7 +291,7 @@ describe('configuration', () => {
ReturnType<BaseQueryFn>,
Parameters<BaseQueryFn>
>()
baseBaseQuery.mockRejectedValue(new Error('rejected'))
baseBaseQuery.mockResolvedValue({ error: 'rejected' })

const baseQuery = retry(baseBaseQuery, {
maxRetries: 8,
Expand Down

0 comments on commit 57316ca

Please sign in to comment.