React-hooks-testing-library: Errors thrown in useEffect are not being caught

Created on 4 Mar 2020  路  6Comments  路  Source: testing-library/react-hooks-testing-library

How to catch an throw Error which is thrown at first render ? for example

import React, { useState, useContext, useCallback } from 'react'

export function useCounter(initialValue = 0) {
  const [count, setCount] = useState(initialValue)

  React.useEffect(()=>{
  if (count > 100) {
    throw Error("It's over 100!")
  },[count])


  return { count}
}

and then i call the following code

import { renderHook, act } from '@testing-library/react-hooks'
import { useCounter } from './counter'

it('should throw when over 100', () => {
  const { result } = renderHook(() => useCounter(101))


  expect(result.error).toEqual(Error("It's over 100!"))
})

So how to catch an error that is thrown automatically on first render?

enhancement

Most helpful comment

Here is an example using an error boundary:

it('should throw when over 100', () => {
  let caughtError = null
  class ErrorBoundary extends React.Component {
    constructor(props) {
      super(props)
      this.state = { hasError: false }
    }

    componentDidCatch(error) {
      this.setState({ hasError: true })
      caughtError = error
    }

    render() {
      return !this.state.hasError && this.props.children
    }
  }

  const wrapper = ({ children }) => <ErrorBoundary>{children}</ErrorBoundary>

  renderHook(() => useCounter(101), { wrapper })

  expect(caughtError).toEqual(Error("It's over 100!"))
})

The following examples both rely on the error being raised out of the renderHook function to be caught:

it('should throw when over 100', () => {
  let caughtError = null

  try {
    renderHook(() => useCounter(101))
  } catch (error) {
    caughtError = error
  }

  expect(caughtError).toEqual(Error("It's over 100!"))
})
it('should throw when over 100', () => {
  expect(() => renderHook(() => useCounter(101))).toThrow(Error("It's over 100!"))
})

I'll let you decide which approach works best for your situation.

I've thought about it a bit more, and when a fix for this goes out, I will treat it as an enhancement and breaking change so that anyone relying on this behaviour for the workaround wont suddenly find they have breaking tests because the error it no longer being raised.

All 6 comments

I took a look at this and it appears as though any errors being thrown in an effect (but not asynchronously) are not getting caught by the error handling in the test component.

A quick test showed that using an actual error boundary _does_ catch this error so we could look into that to support this (and we should support this). Funnily enough, we used to have an error boundary but I removed it because of #50 and #74.

but is it a bug or an enhancement?
Also is there any solution for this as of now? even hacky way would work

I feel like it is a bug, but you're right, it's probably technically an enhancement.

Does wrapping the renderHook call with a try/catch work?

Alternatively you should be able to use the wrapper option of renderHook to add in your own error boundary to intercept the error.

I'm on my phone right now, but I can make an example for you later if that helps?

Sure, i will be waiting

Here is an example using an error boundary:

it('should throw when over 100', () => {
  let caughtError = null
  class ErrorBoundary extends React.Component {
    constructor(props) {
      super(props)
      this.state = { hasError: false }
    }

    componentDidCatch(error) {
      this.setState({ hasError: true })
      caughtError = error
    }

    render() {
      return !this.state.hasError && this.props.children
    }
  }

  const wrapper = ({ children }) => <ErrorBoundary>{children}</ErrorBoundary>

  renderHook(() => useCounter(101), { wrapper })

  expect(caughtError).toEqual(Error("It's over 100!"))
})

The following examples both rely on the error being raised out of the renderHook function to be caught:

it('should throw when over 100', () => {
  let caughtError = null

  try {
    renderHook(() => useCounter(101))
  } catch (error) {
    caughtError = error
  }

  expect(caughtError).toEqual(Error("It's over 100!"))
})
it('should throw when over 100', () => {
  expect(() => renderHook(() => useCounter(101))).toThrow(Error("It's over 100!"))
})

I'll let you decide which approach works best for your situation.

I've thought about it a bit more, and when a fix for this goes out, I will treat it as an enhancement and breaking change so that anyone relying on this behaviour for the workaround wont suddenly find they have breaking tests because the error it no longer being raised.

@mpeyper - thank you!

Was this page helpful?
0 / 5 - 0 ratings