使用 Formik 和 Yup 作为验证模式时 React Native 测试失败

如何解决使用 Formik 和 Yup 作为验证模式时 React Native 测试失败

我正在尝试为使用 Formik 构建的 React Native 组件编写一些测试。这是一个简单的表单,要求输入用户名和密码,我想使用使用 Yup 构建的验证架构。

当我使用模拟器并手动测试表单时,表单的行为符合预期,只有在输入值无效时才会显示错误消息。

但是,当我尝试使用 @testing-library/react-native 编写一些自动化测试时,行为并不是我所期望的。即使提供的值有效,错误消息也会显示在测试中。代码如下:

// App.test.js
import React from 'react';
import { render,act,fireEvent } from '@testing-library/react-native';

import App from '../App';

it('does not show error messages when input values are valid',async () => {
  const {
    findByPlaceholderText,getByPlaceholderText,getByText,queryAllByText,} = render(<App />);

  const usernameInput = await findByPlaceholderText('Username');
  const passwordInput = getByPlaceholderText('Password');
  const submitButton = getByText('Submit');

  await act(async () => {
    fireEvent.changeText(usernameInput,'testUser');
    fireEvent.changeText(passwordInput,'password');
    fireEvent.press(submitButton);
  });

  expect(queryAllByText('This field is required')).toHaveLength(0);
});

// App.js
import React from 'react';
import { TextInput,Button,Text,View } from 'react-native';
import { Formik } from 'formik';
import * as Yup from 'yup';

const Schema = Yup.object().shape({
  username: Yup.string().required('This field is required'),password: Yup.string().required('This field is required'),});

export default function App() {
  return (
    <View>
      <Formik
        initialValues={{ username: '',password: '' }}
        validationSchema={Schema}
        onSubmit={(values) => console.log(values)}>
        {({
          handleChange,handleBlur,handleSubmit,values,errors,touched,validateForm,}) => {
          return (
            <>
              <View>
                <TextInput
                  onChangeText={handleChange('username')}
                  onBlur={handleBlur('username')}
                  value={values.username}
                  placeholder="Username"
                />
                {errors.username && touched.username && (
                  <Text>{errors.username}</Text>
                )}
              </View>

              <View>
                <TextInput
                  onChangeText={handleChange('password')}
                  onBlur={handleBlur('password')}
                  value={values.password}
                  placeholder="Password"
                />
                {errors.password && touched.password && (
                  <Text>{errors.password}</Text>
                )}
              </View>

              <View>
                <Button
                  onPress={handleSubmit}
                  // If I explicitly call validateForm(),the test will pass
                  // onPress={async () => {
                  //   await validateForm();
                  //   handleSubmit();
                  // }}
                  title="Submit"
                />
              </View>
            </>
          );
        }}
      </Formik>
    </View>
  );
}

我不确定我是否正确编写了测试。我认为 Formik 会在调用 handleSubmit 函数自动验证表单。

App.js 中,如果我显式调用 validateForm,测试将通过。但是,仅仅为了满足测试而更改 onPress 处理程序的实现感觉是不对的。也许我错过了围绕这个问题的一些基本概念。任何见解都会有所帮助,谢谢。


软件包版本:

"@testing-library/react-native": "^7.1.0","formik": "^2.2.6","react": "16.13.1","react-native": "0.63.4","yup": "^0.32.8"

解决方法

终于有时间重温这个问题了。虽然我还不能 100% 确定幕后发生了什么,但我认为我发现的结果可能会让其他人受益,所以我会在这里分享。

这个问题与两个子问题交织在一起。第一个与 Promise 的模块中使用的 React Native 有关,第二个与 Formik

验证的异步性质有关

下面是修改后的App.test.js内的代码,保持App.js不变,

// App.test.js
import React from 'react';
import { render,fireEvent,waitFor } from '@testing-library/react-native';

import App from '../App';

it('does not show error messages when input values are valid',async () => {
  const { getByPlaceholderText,getByText,queryAllByText } = render(<App />);

  const usernameInput = getByPlaceholderText('Username');
  const passwordInput = getByPlaceholderText('Password');
  const submitButton = getByText('Submit');

  await waitFor(() => {
    fireEvent.changeText(usernameInput,'testUser');
  });

  await waitFor(() => {
    fireEvent.changeText(passwordInput,'password');
  });

  fireEvent.press(submitButton);

  await waitFor(() => {
    expect(queryAllByText('This field is required')).toHaveLength(0);
  });
});

通常情况下,我们不需要使用 act 来包裹 fireEvent,因为 fireEvent 默认已经用 act 包裹,这要归功于 testing-library .但是,由于Formik在文本值改变后异步执行验证,并且验证函数不受React的调用栈管理,所以我们需要手动将fireEvent调用包裹起来act,或其他方便的方法:waitFor。简而言之,由于其异步性,我们需要用 fireEvent.changeText 包裹 waitFor

但是,将代码改成上述格式并不能解决所有问题。尽管测试通过,但您会遇到与 act 相关的警告。这是一个与 Promise 相关的已知问题,因为 React Native 的 Jest 预设会覆盖原生 Promise。 (https://github.com/facebook/react-native/issues/29303)

如果您注释掉该行

global.Promise = jest.requireActual('promise');

在第 20 行左右的 node_modules/react-native/jest/setup.js 中,此问题将得到解决。但建议直接修改node_modules中的文件。一种解决方法是设置一个笑话预设来恢复原生 Promise,就像这里 (https://github.com/sbalay/without_await/commit/64a76486f31bdc41f5c240d28263285683755938)

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?
Java在半透明框架/面板/组件上重新绘画。
Java“ Class.forName()”和“ Class.forName()。newInstance()”之间有什么区别?
在此环境中不提供编译器。也许是在JRE而不是JDK上运行?
Java用相同的方法在一个类中实现两个接口。哪种接口方法被覆盖?
Java 什么是Runtime.getRuntime()。totalMemory()和freeMemory()?
java.library.path中的java.lang.UnsatisfiedLinkError否*****。dll
JavaFX“位置是必需的。” 即使在同一包装中
Java 导入两个具有相同名称的类。怎么处理?
Java 是否应该在HttpServletResponse.getOutputStream()/。getWriter()上调用.close()?
Java RegEx元字符(。)和普通点?