Skip to content

Commit

Permalink
feat: export helper to manually suppress error output (#571)
Browse files Browse the repository at this point in the history
* feat(console): export helper to manually suppress error output

Fixes #564

* docs: added docs for `suppressErrorOutput`

* fix(types): fix type of `suppressErrorOutput` to make result callable to restore console

* test: add tests for `suppressErrorOutput` export

* test: fix error suppression disabled tests for other renderers
  • Loading branch information
mpeyper committed Mar 1, 2021
1 parent 7f6e4bb commit d8dac20
Show file tree
Hide file tree
Showing 14 changed files with 112 additions and 37 deletions.
31 changes: 29 additions & 2 deletions docs/api-reference.md
Expand Up @@ -12,6 +12,7 @@ route: '/reference/api'
- [`cleanup`](/reference/api#cleanup)
- [`addCleanup`](/reference/api#addcleanup)
- [`removeCleanup`](/reference/api#removecleanup)
- [`suppressErrorOutput`](/reference/api#manually-suppress-output)

---

Expand Down Expand Up @@ -286,6 +287,10 @@ to filter out the unnecessary logging and restore the original version during cl
side-effect can affect tests that also patch `console.error` (e.g. to assert a specific error
message get logged) by replacing their custom implementation as well.

> Please note that this is done automatically if the testing framework you're using supports the
> `beforeEach` and `afterEach` global (like Jest, mocha and Jasmine). If not, you will need to do
> [manual suppression](/reference/api#manually-suppress-output) around the test run.

### Disabling `console.error` filtering

Importing `@testing-library/react-hooks/disable-error-filtering.js` in test setup files disable the
Expand All @@ -303,8 +308,8 @@ module.exports = {
}
```

Alternatively, you can change your test to import from `@testing-library/react-hooks/pure` (or any
of the [other non-pure imports](/installation#pure-imports)) instead of the regular imports.
Alternatively, you can change your test to import from `@testing-library/react-hooks` (or any of the
[other non-pure imports](/installation#pure-imports)) instead of the regular imports.

```diff
- import { renderHook, cleanup, act } from '@testing-library/react-hooks'
Expand All @@ -316,3 +321,25 @@ variable to `true` before importing `@testing-library/react-hooks` will also dis
> Please note that this may result in a significant amount of additional logging in your test
> output.
### Manually suppress output
If you are using [a pure import](/installation#pure-imports), you are running your tests in an
environment that does not support `beforeEach` and `afterEach`, or if the automatic suppression is
not available to you for some other reason, then you can use the `suppressErrorOutput` export to
manually start and top suppress the output:
```ts
import { renderHook, suppressErrorOutput } from '@testing-library/react-hooks/pure'

test('should handle thrown error', () => {
const restoreConsole = suppressErrorOutput()

try {
const { result } = renderHook(() => useCounter())
expect(result.error).toBeDefined()
} finally {
restoreConsole()
}
})
```
38 changes: 21 additions & 17 deletions src/core/console.ts
@@ -1,28 +1,32 @@
import filterConsole from 'filter-console'

function suppressErrorOutput() {
if (process.env.RHTL_DISABLE_ERROR_FILTERING) {
return () => {}
}

return filterConsole(
[
/^The above error occurred in the <TestComponent> component:/, // error boundary output
/^Error: Uncaught .+/ // jsdom output
],
{
methods: ['error']
}
)
}

function enableErrorOutputSuppression() {
// Automatically registers console error suppression and restoration in supported testing frameworks
if (
typeof beforeEach === 'function' &&
typeof afterEach === 'function' &&
!process.env.RHTL_DISABLE_ERROR_FILTERING
) {
let restoreConsole: () => void
if (typeof beforeEach === 'function' && typeof afterEach === 'function') {
let restoreConsole!: () => void

beforeEach(() => {
restoreConsole = filterConsole(
[
/^The above error occurred in the <TestComponent> component:/, // error boundary output
/^Error: Uncaught .+/ // jsdom output
],
{
methods: ['error']
}
)
restoreConsole = suppressErrorOutput()
})

afterEach(() => restoreConsole?.())
afterEach(() => restoreConsole())
}
}

export { enableErrorOutputSuppression }
export { enableErrorOutputSuppression, suppressErrorOutput }
3 changes: 2 additions & 1 deletion src/core/index.ts
Expand Up @@ -2,6 +2,7 @@ import { CreateRenderer, Renderer, RenderResult, RenderHookOptions } from '../ty

import { asyncUtils } from './asyncUtils'
import { cleanup, addCleanup, removeCleanup } from './cleanup'
import { suppressErrorOutput } from './console'

function resultContainer<TValue>() {
const results: Array<{ value?: TValue; error?: Error }> = []
Expand Down Expand Up @@ -81,4 +82,4 @@ function createRenderHook<
return renderHook
}

export { createRenderHook, cleanup, addCleanup, removeCleanup }
export { createRenderHook, cleanup, addCleanup, removeCleanup, suppressErrorOutput }
2 changes: 1 addition & 1 deletion src/dom/__tests__/errorSuppression.disabled.test.ts
Expand Up @@ -5,10 +5,10 @@ describe('error output suppression (disabled) tests', () => {

beforeAll(() => {
process.env.RHTL_DISABLE_ERROR_FILTERING = 'true'
require('..')
})

test('should not patch console.error', () => {
require('..')
expect(console.error).toBe(originalConsoleError)
})
})
Expand Down
20 changes: 17 additions & 3 deletions src/dom/__tests__/errorSuppression.pure.test.ts
@@ -1,15 +1,29 @@
import { ReactHooksRenderer } from '../../types/react'

// This verifies that if pure imports are used
// then we DON'T auto-wire up the afterEach for folks
describe('error output suppression (pure) tests', () => {
const originalConsoleError = console.error

let suppressErrorOutput!: ReactHooksRenderer['suppressErrorOutput']

beforeAll(() => {
require('../pure')
suppressErrorOutput = (require('../pure') as ReactHooksRenderer).suppressErrorOutput
})

test('should not patch console.error', () => {
expect(console.error).toBe(originalConsoleError)
})
})

export {}
test('should manually patch console.error', () => {
const restore = suppressErrorOutput()

try {
expect(console.error).not.toBe(originalConsoleError)
} finally {
restore()
}

expect(console.error).toBe(originalConsoleError)
})
})
2 changes: 1 addition & 1 deletion src/dom/pure.ts
Expand Up @@ -37,6 +37,6 @@ const renderHook = createRenderHook(createDomRenderer)

export { renderHook, act }

export { cleanup, addCleanup, removeCleanup } from '../core'
export { cleanup, addCleanup, removeCleanup, suppressErrorOutput } from '../core'

export * from '../types/react'
2 changes: 1 addition & 1 deletion src/native/__tests__/errorSuppression.disabled.test.ts
Expand Up @@ -5,10 +5,10 @@ describe('error output suppression (disabled) tests', () => {

beforeAll(() => {
process.env.RHTL_DISABLE_ERROR_FILTERING = 'true'
require('..')
})

test('should not patch console.error', () => {
require('..')
expect(console.error).toBe(originalConsoleError)
})
})
Expand Down
20 changes: 17 additions & 3 deletions src/native/__tests__/errorSuppression.pure.test.ts
@@ -1,15 +1,29 @@
import { ReactHooksRenderer } from '../../types/react'

// This verifies that if pure imports are used
// then we DON'T auto-wire up the afterEach for folks
describe('error output suppression (pure) tests', () => {
const originalConsoleError = console.error

let suppressErrorOutput!: ReactHooksRenderer['suppressErrorOutput']

beforeAll(() => {
require('../pure')
suppressErrorOutput = (require('../pure') as ReactHooksRenderer).suppressErrorOutput
})

test('should not patch console.error', () => {
expect(console.error).toBe(originalConsoleError)
})
})

export {}
test('should manually patch console.error', () => {
const restore = suppressErrorOutput()

try {
expect(console.error).not.toBe(originalConsoleError)
} finally {
restore()
}

expect(console.error).toBe(originalConsoleError)
})
})
2 changes: 1 addition & 1 deletion src/native/pure.ts
Expand Up @@ -36,6 +36,6 @@ const renderHook = createRenderHook(createNativeRenderer)

export { renderHook, act }

export { cleanup, addCleanup, removeCleanup } from '../core'
export { cleanup, addCleanup, removeCleanup, suppressErrorOutput } from '../core'

export * from '../types/react'
4 changes: 2 additions & 2 deletions src/pure.ts
Expand Up @@ -32,8 +32,8 @@ function getRenderer() {
}
}

const { renderHook, act, cleanup, addCleanup, removeCleanup } = getRenderer()
const { renderHook, act, cleanup, addCleanup, removeCleanup, suppressErrorOutput } = getRenderer()

export { renderHook, act, cleanup, addCleanup, removeCleanup }
export { renderHook, act, cleanup, addCleanup, removeCleanup, suppressErrorOutput }

export * from './types/react'
2 changes: 1 addition & 1 deletion src/server/__tests__/errorSuppression.disabled.test.ts
Expand Up @@ -5,10 +5,10 @@ describe('error output suppression (disabled) tests', () => {

beforeAll(() => {
process.env.RHTL_DISABLE_ERROR_FILTERING = 'true'
require('..')
})

test('should not patch console.error', () => {
require('..')
expect(console.error).toBe(originalConsoleError)
})
})
Expand Down
20 changes: 17 additions & 3 deletions src/server/__tests__/errorSuppression.pure.test.ts
@@ -1,15 +1,29 @@
import { ReactHooksRenderer } from '../../types/react'

// This verifies that if pure imports are used
// then we DON'T auto-wire up the afterEach for folks
describe('error output suppression (pure) tests', () => {
const originalConsoleError = console.error

let suppressErrorOutput!: ReactHooksRenderer['suppressErrorOutput']

beforeAll(() => {
require('../pure')
suppressErrorOutput = (require('../pure') as ReactHooksRenderer).suppressErrorOutput
})

test('should not patch console.error', () => {
expect(console.error).toBe(originalConsoleError)
})
})

export {}
test('should manually patch console.error', () => {
const restore = suppressErrorOutput()

try {
expect(console.error).not.toBe(originalConsoleError)
} finally {
restore()
}

expect(console.error).toBe(originalConsoleError)
})
})
2 changes: 1 addition & 1 deletion src/server/pure.ts
Expand Up @@ -61,6 +61,6 @@ const renderHook = createRenderHook(createServerRenderer)

export { renderHook, act }

export { cleanup, addCleanup, removeCleanup } from '../core'
export { cleanup, addCleanup, removeCleanup, suppressErrorOutput } from '../core'

export * from '../types/react'
1 change: 1 addition & 0 deletions src/types/react.ts
Expand Up @@ -26,6 +26,7 @@ export type ReactHooksRenderer = {
cleanup: () => void
addCleanup: (callback: CleanupCallback) => () => void
removeCleanup: (callback: CleanupCallback) => void
suppressErrorOutput: () => () => void
}

export * from '.'

0 comments on commit d8dac20

Please sign in to comment.