reactjs - 使用 React-testing 库提交 Formik 表单

标签 reactjs formik react-testing-library

我正在寻找触发登录表单的提交处理程序。然而,由于某种原因,组件的实际处理程序被触发(调用外部 api),而不是调用我的模拟函数。如何确保我的模拟处理程序被调用?

感兴趣的三个组件如下(演示、容器和测试套件)

登录表单.js

import { Formik, Form, Field } from 'formik';
import { CustomInput } from '..';

const LoginForm = ({ initialValues, handleSubmit, validate }) => {
  return (
    <Formik
      initialValues={initialValues}
      validate={validate}
      onSubmit={handleSubmit}
    >
      {({ isSubmitting, handleSubmit }) => {
        return (
        <Form onSubmit={handleSubmit}>
          <div className="d-flex flex-column justify-content-center align-items-center">
            <Field
              data-testid="usernameOrEmail"
              type="text"
              name="identifier"
              placeholder="Username/Email"
              component={CustomInput}
              inputClass="mb-4 mt-2 text-monospace"
            />
            <Field
              data-testid="login-password"
              type="password"
              name="password"
              placeholder="Password"
              component={CustomInput}
              inputClass="mb-4 mt-4 text-monospace"
            />
            <button
              data-testid="login-button"
              className="btn btn-primary btn-lg mt-3 text-monospace"
              type="submit"
              disabled={isSubmitting}
              style={{ textTransform: 'uppercase', minWidth: '12rem' }}
            >
              Submit
            </button>
          </div>
        </Form>
      )}}
    </Formik>
  );
};

export default LoginForm;

登录页面.js

import React, { useContext } from 'react';
import { loginUser } from '../../services';
import { userContext } from '../../contexts';
import { loginValidator } from '../../helpers';
import { setAuthorizationToken, renderAlert } from '../../utils';
import LoginForm from './login-form';

const INITIAL_VALUES = { identifier: '', password: '' };

const LoginPage = props => {
  const { handleUserData, handleAuthStatus } = useContext(userContext);

  const handleSubmit = async (values, { setSubmitting }) => {
    try {
      const result = await loginUser(values);
      handleAuthStatus(true);
      handleUserData(result.data);
      setAuthorizationToken(result.data.token);
      props.history.push('/habits');
      renderAlert('success', 'Login Successful');
    } catch (err) {
      renderAlert('error', err.message);
    }
    setSubmitting(false);
  };

  return (
    <LoginForm
      initialValues={INITIAL_VALUES}
      validate={values => loginValidator(values)}
      handleSubmit={handleSubmit}
    />
  );
};

export default LoginPage;

LoginPage.spec.js

import React from 'react';
import { cleanup, getByTestId, fireEvent, wait } from 'react-testing-library';
import { renderWithRouter } from '../../../helpers';
import LoginPage from '../login-page';

afterEach(cleanup);
const handleSubmit = jest.fn();

test('<LoginPage /> renders with blank fields', () => {
  const { container } = renderWithRouter(<LoginPage />);

  const usernameOrEmailNode = getByTestId(container, 'usernameOrEmail');
  const passwordNode = getByTestId(container, 'login-password');
  const submitButtonNode = getByTestId(container, 'login-button');

  expect(usernameOrEmailNode.tagName).toBe('INPUT');
  expect(passwordNode.tagName).toBe('INPUT');
  expect(submitButtonNode.tagName).toBe('BUTTON');
  expect(usernameOrEmailNode.getAttribute('value')).toBe('');
  expect(passwordNode.getAttribute('value')).toBe('');
});

test('Clicking the submit button after entering values', async () => {
  const { container } = renderWithRouter(<LoginPage handleSubmit={handleSubmit} />);

  const usernameOrEmailNode = getByTestId(container, 'usernameOrEmail');
  const passwordNode = getByTestId(container, 'login-password');
  const submitButtonNode = getByTestId(container, 'login-button');

  fireEvent.change(usernameOrEmailNode, { target: { value: fakeUser.username }});
  fireEvent.change(passwordNode, { target: { value: fakeUser.password }});
  fireEvent.click(submitButtonNode);

  await wait(() => {
    expect(handleSubmit).toHaveBeenCalledTimes(1);
  });


  expect(usernameOrEmailNode.tagName).toBe('INPUT');
  expect(passwordNode.tagName).toBe('INPUT');
  expect(submitButtonNode.tagName).toBe('BUTTON');
  expect(usernameOrEmailNode.getAttribute('value')).toBe('');
  expect(passwordNode.getAttribute('value')).toBe('');
});```

最佳答案

要回答您的问题,您需要首先在 LoginPage.js 外部访问 handleSubmit 常量,以便可以对其进行模拟然后进行测试。例如,

LoginPage.js

export const handleSubmit = async (values, { setSubmitting }) => {
 ... code to handle submission
})

在您的测试中 - LoginPage.spec.js

jest.unmock('./login-page');
import LoginPage, otherFunctions from '../login-page'
otherFunctions.handleSubmit = jest.fn();

...
test('Clicking the submit button after entering values', () => {
  ...
  fireEvent.click(submitButtonNode);
  expect(handleSubmit).toHaveBeenCalledTimes(1);
})

希望以上内容能解决您的问题。

But, going by the philosophy of unit testing, the above components must not be tested the way you are doing it. Instead your test setup should be like this -

  1. Add a new test file called LoginForm.spec.js that tests your LoginForm component. You would test the following in this -
    1. Check if all input fields have been rendered.
    2. Check if the correct handler is called on submit and with the correct parameters.
  2. The existing test file called LoginPage.spec.js would then only test if the particular form was rendered and then you could also test what the handleSubmit method does individually.

I believe the above would make your tests more clearer and readable too, because of the separation of concerns and would also allow you to test more edge cases.

关于reactjs - 使用 React-testing 库提交 Formik 表单,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54842033/

相关文章:

reactjs - 如何使用 Formik 管理嵌套的复杂对象

reactjs - 在测试 React 组件时模拟 Redux 存储?

javascript - Apollo MockedProvider 总是返回未定义的数据字段,即使在加载之后

reactjs - React 测试库如何断言子属性和 Redux 存储更新?

reactjs - React Router v4 支持 React 16 (Fiber) 吗?

javascript - 为什么在使用 react-input-mask 和 formik 时光标会跳到输入的末尾?

javascript - 倒计时在某些情况下不起作用

javascript - 在 react-draft-wysiwyg 上使用 formik

javascript - 无法在 react 组件中定义箭头方法

css - 如何在 create-react-app 元素中使用本地字体文件