使用 Formik 和 Yup 作为验证模式时,React Native 测试失败
React Native test failed when using Formik and Yup as validation schema
我正在尝试为使用 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% 确定幕后发生了什么,但我认为我发现的结果可能会使其他人受益,所以我会在这里分享。
这个问题与两个子问题交织在一起。第一个与 React Native
模块中使用的 Promise
有关,第二个与 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
包裹了 [=21] =].但是,由于 Formik
在文本值更改后异步执行验证,并且验证函数不受 React
的调用堆栈管理,因此我们需要手动包装 fireEvent
调用act
,或另一种方便的方法:waitFor
。简而言之,由于其异步性,我们需要用 waitFor
包装 fireEvent.changeText
。
但是,将代码改成上面的格式并不能解决所有的问题。虽然测试通过了,但是您会遇到与 act
相关的警告。这是一个与 Promise
相关的已知问题,因为 React Native 的 Jest 预设会覆盖原生 Promise。 (https://github.com/facebook/react-native/issues/29303)
如果注释掉该行
global.Promise = jest.requireActual('promise');
在node_modules/react-native/jest/setup.js
的第20行左右,这个问题就解决了。但直接修改 node_modules
中的文件 不 推荐。解决方法是设置一个笑话预设来恢复原生 Promise,就像这里 (https://github.com/sbalay/without_await/commit/64a76486f31bdc41f5c240d28263285683755938)
我正在尝试为使用 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% 确定幕后发生了什么,但我认为我发现的结果可能会使其他人受益,所以我会在这里分享。
这个问题与两个子问题交织在一起。第一个与 React Native
模块中使用的 Promise
有关,第二个与 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
包裹了 [=21] =].但是,由于 Formik
在文本值更改后异步执行验证,并且验证函数不受 React
的调用堆栈管理,因此我们需要手动包装 fireEvent
调用act
,或另一种方便的方法:waitFor
。简而言之,由于其异步性,我们需要用 waitFor
包装 fireEvent.changeText
。
但是,将代码改成上面的格式并不能解决所有的问题。虽然测试通过了,但是您会遇到与 act
相关的警告。这是一个与 Promise
相关的已知问题,因为 React Native 的 Jest 预设会覆盖原生 Promise。 (https://github.com/facebook/react-native/issues/29303)
如果注释掉该行
global.Promise = jest.requireActual('promise');
在node_modules/react-native/jest/setup.js
的第20行左右,这个问题就解决了。但直接修改 node_modules
中的文件 不 推荐。解决方法是设置一个笑话预设来恢复原生 Promise,就像这里 (https://github.com/sbalay/without_await/commit/64a76486f31bdc41f5c240d28263285683755938)