zoukankan      html  css  js  c++  java
  • [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()
  • 相关阅读:
    重链剖分的总结与模板
    PBDS学习笔记(一)
    LCT 第一题 洛谷模板
    2018年暑假第四次周赛-图论部分题解
    后缀数组求不同子串的个数
    Codeforces Round #106 (Div. 2) Coloring Brackets(区间DP)
    Codeforces Round #510 (Div. 2) D. Petya and Array (权值线段树)
    HDU 3974 Assign the task (dfs序+线段树)
    Manthan, Codefest 18 (rated, Div. 1 + Div. 2) D.Valid BFS? (模拟)
    POJ
  • 原文地址:https://www.cnblogs.com/Answer1215/p/12814652.html
Copyright © 2011-2022 走看看