[React Testing] Ensure Error Boundaries Can Successfully Recover from Errors


Our error boundary has some other use cases that it supports and we should try to make sure our tests cover all those use cases, so let’s add a test to make sure the recovery feature of our error boundary works properly.

 

Error bundary:

import React from 'react'
import {reportError} from './api'

class ErrorBoundary extends React.Component {
  state = {hasError: false}
  componentDidCatch(error, info) {
    this.setState({hasError: true})
    reportError(error, info)
  }
  tryAgain = () => this.setState({hasError: false})
  render() {
    return this.state.hasError ? (
      <div>
        <div role="alert">There was a problem.</div>{' '}
        <button onClick={this.tryAgain}>Try again?</button>
      </div>
    ) : (
      this.props.children
    )
  }
}

export {ErrorBoundary}

 

Test:

import React from 'react'
import { render, fireEvent } from '@testing-library/react' import '@testing-library/jest-dom/extend-expect'
import ErrorBoundary from './error-boundary'
import { reportError as mockReportError } from './components/extra/api'

function Bomb({ shouldThrow }) {
  if (shouldThrow) {
    throw new Error('Bomb')
  } else {
    return null
  }
}

jest.mock('./components/extra/api')

beforeAll(() => {
  // do log out any error message
  jest.spyOn(console, 'error').mockImplementation(() => {})
})

afterAll(() => {
  console.error.mockRestore()
})

test('calls reportError and renders that there was a problem', () => {
  mockReportError.mockResolvedValueOnce({ success: true })
  const { rerender, getByRole, getByText, queryByText, queryByRole } = render(
    <ErrorBoundary>
      <Bomb />
    </ErrorBoundary>,
  )

  rerender(
    <ErrorBoundary>
      <Bomb shouldThrow={true} />
    </ErrorBoundary>,
  )

  const error = expect.any(Error)
  const errorInfo = { componentStack: expect.stringContaining('Bomb') }
  expect(mockReportError).toHaveBeenCalledWith(error, errorInfo)
  expect(mockReportError).toHaveBeenCalledTimes(1)

  expect(console.error).toHaveBeenCalledTimes(2)

  expect(getByRole('alert').textContent).toMatchInlineSnapshot(
    `"Something went wrong."`,
  )

  rerender(
    <ErrorBoundary>
      <Bomb />
    </ErrorBoundary>,
  )

  const button = getByText(/try again/i)
  fireEvent.click(button)
  // reset the mock state
  mockReportError.mockClear()
  console.error.mockClear()

  expect(mockReportError).not.toHaveBeenCalled()
  expect(console.error).not.toHaveBeenCalled()
  expect(queryByRole('alert')).not.toBeInTheDocument()
  expect(queryByText(/try again/i)).not.toBeInTheDocument()
})

test('calls reportError and renders that there was a problem (clean up with wrapper)', () => {
  mockReportError.mockResolvedValueOnce({ success: true })
  const {
    rerender,
    getByRole,
    getByText,
    queryByText,
    queryByRole,
  } = render(<Bomb />, { wrapper: ErrorBoundary })

  rerender(<Bomb shouldThrow={true} />)

  const error = expect.any(Error)
  const errorInfo = { componentStack: expect.stringContaining('Bomb') }
  expect(mockReportError).toHaveBeenCalledWith(error, errorInfo)
  expect(mockReportError).toHaveBeenCalledTimes(1)

  expect(console.error).toHaveBeenCalledTimes(2)

  expect(queryByRole('alert')).toBeInTheDocument()
  expect(getByRole('alert').textContent).toMatchInlineSnapshot(
    `"Something went wrong."`,
  )

  rerender(<Bomb shouldThrow={false} />)

  const button = getByText(/try again/i)
  fireEvent.click(button)
  // reset the mock state
 mockReportError.mockClear() console.error.mockClear()

  expect(mockReportError).not.toHaveBeenCalled()
  expect(console.error).not.toHaveBeenCalled()
  expect(queryByRole('alert')).not.toBeInTheDocument()
  expect(queryByText(/try again/i)).not.toBeInTheDocument()
})

afterEach(() => {
  jest.clearAllMocks()
})

 

 

Notice:

After sucessfully rerender the DOM, we want to make sure, 'reportError' and 'console.error' haven't been called, but previously we have called once, so we have to clear the mock state by doing:

  // reset the mock state
  mockReportError.mockClear()
  console.error.mockClear()

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM