提交表单时如何测试useFormik

How can I test useFormik when the form is submitting

如下所示,Form 组件正在使用 useFormik 挂钩。该组件满足了我的所有需求,但在测试开始时我很挣扎,特别是在提交表单时。

Form.tsx

import {
  TextField,
  Button,
  Box,
  Typography,
  useTheme,
  Snackbar
} from '@material-ui/core'
import { useState } from 'react'
import { useFormik } from 'formik'
import { object, string, SchemaOf, ref as yupRef } from 'yup'
import axios from 'axios'
import { useRouter } from 'next/router'
import { useFocus } from '@hooks/useFocus'
import { FormLink } from '@components/common/FormLink'

export interface FormTypes {
  username: string
  email: string
  password: string
  confirmPassword: string
}

export const validationSchema: SchemaOf<FormTypes> = object({
  username: string()
    .min(2, 'Username should be of minimum 2 characters')
    .max(25, 'Username should be of maximum 25 characters')
    .required('Name is required'),
  email: string().email('Enter a valid email').required('Email is required'),
  password: string()
    .min(8, 'Password should be of minimum 8 characters')
    .required('Password is required'),
  confirmPassword: string().oneOf(
    [yupRef('password'), null],
    'Passwords must match'
  )
})

export const Form = () => {
  const theme = useTheme()
  const ref = useFocus()
  const [open, setOpen] = useState(false)
  const [errorMessage, setErrorMessage] = useState('')
  const router = useRouter()

  const formik = useFormik<FormTypes>({
    initialValues: {
      username: '',
      email: '',
      password: '',
      confirmPassword: ''
    },
    validationSchema,
    onSubmit: async (values, { setSubmitting }) => {
      setSubmitting(true)

      const res = await axios.post('/api/register', values)
      if (res.data.success) {
        router.push('dashboard')
      } else {
        setOpen(true)
        setErrorMessage(res.data.message)
      }
      setSubmitting(false)
    }
  })

  const handleClose = () => {
    setOpen(false)
  }

  return (
    <Box
      width="55%"
      p={theme.spacing(6, 8)}
      borderRadius={16}
      bgcolor={theme.palette.grey[200]}
      boxShadow={theme.shadows[15]}
      display="grid"
    >
      <Box clone alignSelf="center" style={{ marginBottom: theme.spacing(2) }}>
        <Typography component="h3" variant="h5" color="primary">
          Sign up
        </Typography>
      </Box>
      <Box clone display="grid" gridGap={theme.spacing(1)}>
        <form onSubmit={formik.handleSubmit}>
          <TextField
            id="username"
            label="Username"
            name="username"
            inputRef={ref}
            value={formik.values.username}
            onChange={formik.handleChange}
            error={formik.touched.username && Boolean(formik.errors.username)}
            helperText={formik.touched.username && formik.errors.username}
          />
          <TextField
            id="email"
            label="Email"
            name="email"
            value={formik.values.email}
            onChange={formik.handleChange}
            error={formik.touched.email && Boolean(formik.errors.email)}
            helperText={formik.touched.email && formik.errors.email}
          />
          <TextField
            id="password"
            label="Password"
            name="password"
            type="password"
            value={formik.values.password}
            onChange={formik.handleChange}
            error={formik.touched.password && Boolean(formik.errors.password)}
            helperText={formik.touched.password && formik.errors.password}
          />
          <TextField
            id="confirmPassword"
            label="Confirm Password"
            name="confirmPassword"
            type="password"
            value={formik.values.confirmPassword}
            onChange={formik.handleChange}
            error={
              formik.touched.confirmPassword &&
              Boolean(formik.errors.confirmPassword)
            }
            helperText={
              formik.touched.confirmPassword && formik.errors.confirmPassword
            }
          />
          <Box
            clone
            justifySelf="start"
            alignSelf="center"
            style={{
              borderRadius: 24,
              padding: theme.spacing(1.5, 5),
              marginTop: theme.spacing(2)
            }}
          >
            <Button
              type="submit"
              variant="contained"
              color="primary"
              disabled={formik.isSubmitting}
            >
              {formik.isSubmitting ? 'Loading...' : 'sign up'}
            </Button>
          </Box>
        </form>
      </Box>
      <Typography style={{ marginTop: theme.spacing(2) }} variant="body1">
        Already registered? <FormLink href="/login">Login</FormLink>
      </Typography>
      <Snackbar
        open={open}
        autoHideDuration={3000}
        message={errorMessage}
        onClose={handleClose}
      />
    </Box>
  )
}


Form.test.tsx

import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { Form } from './Form'

describe('Signup form', () => {
  it('should submit the signup form', async () => {
    render(<Form />)

    userEvent.type(screen.getByLabelText(/username/i), 'John')
    userEvent.type(screen.getByLabelText(/email/i), 'john.dee@someemail.com')
    userEvent.type(screen.getByLabelText(/^password$/i), 'Dee123456')
    userEvent.type(screen.getByLabelText(/^confirm password$/i), 'Dee123456')

    await waitFor(() =>
      /* ?????? */
      expect('').toHaveBeenCalledWith({
        username: 'John',
        email: 'john.dee@someemail.com',
        password: 'Dee123456',
        confirmPassword: 'Dee123456'
      })
    )
  })
})

我是一个使用测试的初学者,很难说,但我需要在提交表单时进行测试并检查数据。我使用像

这样的道具找到了解决方案
const handleSubmit = jest.fn()

我无法应用该代码,因为我没有在表单组件中使用道具。

React 测试库的主要目标是允许您编写类似于真实用户使用软件的方式的测试。在这种情况下,用户不会看到 handleSubmit 函数,但他们在单击提交后会看到“正在加载...”文本。因此,让我们像真实用户一样进行测试并检查一下:

describe('Signup form', () => {
  it('should submit the signup form', async () => {
    render(<Form />)

    userEvent.type(screen.getByLabelText(/username/i), 'John')
    userEvent.type(screen.getByLabelText(/email/i), 'john.dee@someemail.com')
    userEvent.type(screen.getByLabelText(/^password$/i), 'Dee123456')
    userEvent.type(screen.getByLabelText(/^confirm password$/i), 'Dee123456')

    // Click "sign up" button, like a real user would.
    screen.getByRole('button', { name: 'sign up' }).click();

    // Check to see if the button text changes, like a real user would.
    await waitFor(() =>
      expect(screen.getByRole('button', { name: 'Loading...' })).toBeInTheDocument();
    )
  })
})

你会注意到我在这里使用了 getByRole。这是查找元素的第一最佳方式,因为它鼓励您编写更易于访问的代码,而且真实用户通常也是这样找到您的组件的(按钮、输入、复选框等)。看看 Kent 的 this order of priority to give you a better idea on which query to use. Also make sure to read this article C Dodds,React 测试库的创建者。