• [React Testing] Write React Application Integration Tests with React Testing Library


    You can get a huge amount of confidence and coverage from integration tests that test an entire page, or even your entire app. Let’s write a test that renders our whole app using React Testing Library and navigate around it like a normal user would. These tests are typically a bit longer, but they provide a huge amount of value.

     App.js
    import React from 'react'
    import { BrowserRouter as Router, Route, Link, Switch } from 'react-router-dom'
    import { submitForm } from './api'
    
    const MultiPageForm = React.createContext()
    
    function MultiPageFormProvider({ initialValues = {}, ...props }) {
      const [initState] = React.useState(initialValues)
      const [form, setFormValues] = React.useReducer(
        (s, a) => ({ ...s, ...a }),
        initState,
      )
      const resetForm = () => setFormValues(initialValues)
      return (
        <MultiPageForm.Provider
          value={{ form, setFormValues, resetForm }}
          {...props}
        />
      )
    }
    
    function useMultiPageForm() {
      const context = React.useContext(MultiPageForm)
      if (!context) {
        throw new Error(
          'useMultiPageForm must be used within a MiltiPageFormProvider',
        )
      }
      return context
    }
    
    function Main() {
      return (
        <>
          <h1>Welcome home</h1>
          <Link to="/page-1">Fill out the form</Link>
        </>
      )
    }
    
    function Page1({ history }) {
      const { form, setFormValues } = useMultiPageForm()
      return (
        <>
          <h2>Page 1</h2>
          <form
            onSubmit={(e) => {
              e.preventDefault()
              history.push('/page-2')
            }}
          >
            <label htmlFor="food">Favorite Food</label>
            <input
              id="food"
              value={form.food}
              onChange={(e) => setFormValues({ food: e.target.value })}
            />
          </form>
          <Link to="/">Go Home</Link> | <Link to="/page-2">Next</Link>
        </>
      )
    }
    
    function Page2({ history }) {
      const { form, setFormValues } = useMultiPageForm()
      return (
        <>
          <h2>Page 2</h2>
          <form
            onSubmit={(e) => {
              e.preventDefault()
              history.push('/confirm')
            }}
          >
            <label htmlFor="drink">Favorite Drink</label>
            <input
              id="drink"
              value={form.drink}
              onChange={(e) => setFormValues({ drink: e.target.value })}
            />
          </form>
          <Link to="/page-1">Go Back</Link> | <Link to="/confirm">Review</Link>
        </>
      )
    }
    
    function Confirm({ history }) {
      const { form, resetForm } = useMultiPageForm()
      function handleConfirmClick() {
        submitForm(form).then(
          () => {
            resetForm()
            history.push('/success')
          },
          (error) => {
            history.push('/error', { state: { error } })
          },
        )
      }
      return (
        <>
          <h2>Confirm</h2>
          <div>
            <strong>Please confirm your choices</strong>
          </div>
          <div>
            <strong id="food-label">Favorite Food</strong>:{' '}
            <span aria-labelledby="food-label">{form.food}</span>
          </div>
          <div>
            <strong id="drink-label">Favorite Drink</strong>:{' '}
            <span aria-labelledby="drink-label">{form.drink}</span>
          </div>
          <Link to="/page-2">Go Back</Link> |{' '}
          <button onClick={handleConfirmClick}>Confirm</button>
        </>
      )
    }
    
    function Success() {
      return (
        <>
          <h2>Congrats. You did it.</h2>
          <div>
            <Link to="/">Go home</Link>
          </div>
        </>
      )
    }
    
    function Error({
      location: {
        state: { error },
      },
    }) {
      return (
        <>
          <div>Oh no. There was an error.</div>
          <pre>{error.message}</pre>
          <Link to="/">Go Home</Link>
          <Link to="/confirm">Try again</Link>
        </>
      )
    }
    
    function App() {
      return (
        <MultiPageFormProvider initialValues={{ food: '', drink: '' }}>
          <Router>
            <Switch>
              <Route path="/page-1" component={Page1} />
              <Route path="/page-2" component={Page2} />
              <Route path="/confirm" component={Confirm} />
              <Route path="/success" component={Success} />
              <Route path="/error" component={Error} />
              <Route component={Main} />
            </Switch>
          </Router>
        </MultiPageFormProvider>
      )
    }
    
    export default App
    

      

    Test:

    import React from 'react'
    import { render, fireEvent } from '@testing-library/react'
    import { submitForm as mockSubmitForm } from '../extra/api'
    import App from '../extra/app'
    import '@testing-library/jest-dom/extend-expect'
    
    jest.mock('../extra/api')
    
    test('Can fill out a form across multiple pages', async () => {
      mockSubmitForm.mockResolvedValueOnce({ success: true })
      const testData = { food: 'test food', drink: 'test drink' }
      const { getByLabelText, getByText, findByText } = render(<App />)
    
      // use regex
      fireEvent.click(getByText(/fill.*form/i))
    
      // pass the data
      fireEvent.change(getByLabelText(/food/i), {
        target: { value: testData.food },
      })
      fireEvent.click(getByText(/next/i))
    
      fireEvent.change(getByLabelText(/drink/i), {
        target: { value: testData.drink },
      })
      fireEvent.click(getByText(/review/i))
    
      expect(getByLabelText(/food/i)).toHaveTextContent(testData.food)
      expect(getByLabelText(/drink/i)).toHaveTextContent(testData.drink)
    
      // solve multi confirm text, add selector
      fireEvent.click(getByText(/confirm/i, { selector: 'button' }))
    
      expect(mockSubmitForm).toHaveBeenCalledWith(testData)
      expect(mockSubmitForm).toHaveBeenCalledTimes(1)
    
      // findBy*, using await
      fireEvent.click(await findByText(/home/i))
    
      expect(getByText(/welcome home/i)).toBeInTheDocument()
    })

    Imporved version: by findBy*

    By using some of the get queries, we’re assuming that those elements will be available on the page right when we execute the query. This is a bit of an implementation detail and it’d be cool if we could not make that assumption in our test. Let’s swap all those for find queries.

    jest.mock('../extra/api')
    
    afterEach(() => {
      jest.clearAllMocks()
    })
    
    test('Can fill out a form across multiple pages', async () => {
      mockSubmitForm.mockResolvedValueOnce({ success: true })
      const testData = { food: 'test food', drink: 'test drink' }
      const { findByLabelText, findByText } = render(<App />)
    
      fireEvent.click(await findByText(/fill.*form/i))
    
      fireEvent.change(await findByLabelText(/food/i), {
        target: { value: testData.food },
      })
      fireEvent.click(await findByText(/next/i))
    
      fireEvent.change(await findByLabelText(/drink/i), {
        target: { value: testData.drink },
      })
      fireEvent.click(await findByText(/review/i))
    
      expect(await findByLabelText(/food/i)).toHaveTextContent(testData.food)
      expect(await findByLabelText(/drink/i)).toHaveTextContent(testData.drink)
    
      fireEvent.click(await findByText(/confirm/i, { selector: 'button' }))
    
      expect(mockSubmitForm).toHaveBeenCalledWith(testData)
      expect(mockSubmitForm).toHaveBeenCalledTimes(1)
    
      fireEvent.click(await findByText(/home/i))
    
      expect(await findByText(/welcome home/i)).toBeInTheDocument()
    })

    Improved version: user-event

    import user from '@testing-library/user-event'
    
    test('Can fill out a form across multiple pages', async () => {
      mockSubmitForm.mockResolvedValueOnce({ success: true })
      const testData = { food: 'test food', drink: 'test drink' }
      const { findByLabelText, findByText } = render(<App />)
    
      user.click(await findByText(/fill.*form/i))
    
      user.type(await findByLabelText(/food/i), testData.food)
      user.click(await findByText(/next/i))
    
      user.type(await findByLabelText(/drink/i), testData.drink)
      user.click(await findByText(/review/i))
    
      expect(await findByLabelText(/food/i)).toHaveTextContent(testData.food)
      expect(await findByLabelText(/drink/i)).toHaveTextContent(testData.drink)
    
      user.click(await findByText(/confirm/i, { selector: 'button' }))
    
      expect(mockSubmitForm).toHaveBeenCalledWith(testData)
      expect(mockSubmitForm).toHaveBeenCalledTimes(1)
    
      user.click(await findByText(/home/i))
    
      expect(await findByText(/welcome home/i)).toBeInTheDocument()
    })
  • 相关阅读:
    转 Unicdoe【真正的完整码表】对照表
    golang中,unsafe.sizeof到底是干嘛的?
    转载 Golang []byte与string转换的一个误区
    python异常处理--try except else raise finally
    微信静默授权增加浏览器历史记录导致跳转死循环的解决方案
    package-lock.json,我们应该了解
    webpack实现静态资源缓存的那点事
    webpack+babel项目在IE下报Promise未定义错误引出的思考
    CORS预检请求详谈
    谈谈form-data请求格式
  • 原文地址:https://www.cnblogs.com/Answer1215/p/12825037.html
Copyright © 2020-2023  润新知