I am struggling with act
errors when it comes to testing my React Native application using JEST
and testing-library
.
I have a simple Formik form and I am trying to test if the validation works.
My screen I am testing:
const SignInScreen: React.FC = () => {
const { translations } = useContext(LocalizationContext);
const [signIn, { isLoading, isError }] = useSignInMutation();
const initialValues: SignInRequest = {
name: '',
password: ''
};
const validationSchema = Yup.object({
name: Yup.string()
.required(translations['required'])
.max(15, ({max}) => translations.formatString(
translations['validationNCharOrLess'], { n: max })),
password: Yup.string()
.required(translations['required'])
});
const handleSubmit = async (values: SignInRequest, formikHelpers: FormikHelpers<SignInRequest>) => {
await signIn(values)
.unwrap()
.catch(e => {
if ('data' in e && e.data &&
'errors' in e.data && e.data.errors)
{
formikHelpers.setErrors(mapErrors(e.data.errors));
}
})
}
return (
<SafeAreaView
testID={tiConfig.SAFE_AREA_VIEW}
style={{ flex: 1 }}>
<View
testID={tiConfig.SIGN_IN_SCREEN}
style={styles.container}>
<View>
<Text>{translations['signIn']}</Text>
<Formik
initialValues={initialValues}
validationSchema={validationSchema}
onSubmit={handleSubmit}>
{
({ values, errors, handleSubmit, handleChange }) => (
<View>
<Input
testID={tiConfig.SIGN_IN_USERNAME_INPUT}
value={values.name}
placeholder={translations['username']}
onChangeText={handleChange('name')}
errorMessage={errors.name} />
<Input
testID={tiConfig.SIGN_IN_PASSWORD_INPUT}
value={values.password}
placeholder={translations['password']}
onChangeText={handleChange('password')}
errorMessage={errors.password}
secureTextEntry />
{
isError ?
<View>
<Text testID={tiConfig.SIGN_IN_SERVER_ERROR}>
{ translations['somethingWentWrongTryAgainLater'] }
</Text>
</View>
: null
}
<Button
testID={tiConfig.SIGN_IN_SUBMIT}
title={translations['signIn']}
onPress={handleSubmit}
loading={isLoading} />
</View>
)
}
</Formik>
</View>
</View>
</SafeAreaView>
);
}
My test:
// tiConfig is a json with my test id constants
test.only("Sign in username field validates correctly", async () => {
const component = render(<SignInScreen />);
const usernameInput = await component.findByTestId(tiConfig.SIGN_IN_USERNAME_INPUT);
// A bit weird way to find the error text with a nesting but it works for now
const errorMessage = usernameInput
.parent!.parent!.parent!.parent!.parent!.parent!.findByType(Text);
const submit = component.getByTestId(tiConfig.SIGN_IN_SUBMIT);
fireEvent.press(submit);
await waitFor(() => expect(errorMessage.props.children).toBe(translations.required));
fireEvent.changeText(usernameInput, "username");
await waitFor(() => expect(errorMessage).toBeEmpty());
fireEvent.changeText(usernameInput, "toolongusernameshouldntbeallowed");
await waitFor(() => expect(errorMessage).not.toBeEmpty());
});
Warning:
Warning: You called act(async () => ...) without await. This could lead to unexpected testing behaviour, interleaving multiple act calls and mixing their scopes. You should - await act(async () => ...);
at registerError (node_modules/react-native/Libraries/LogBox/LogBox.js:172:15)
at errorImpl (node_modules/react-native/Libraries/LogBox/LogBox.js:58:22)
at console.Object.<anonymous>.console.error (node_modules/react-native/Libraries/LogBox/LogBox.js:32:14)
at printWarning (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:68:30)
at error (node_modules/react-test-renderer/cjs/react-test-renderer.development.js:44:5)
at node_modules/react-test-renderer/cjs/react-test-renderer.development.js:15297:13
at tryCallOne (node_modules/promise/lib/core.js:37:12)
I get this warning 3 times
Without waitFor
my test doesn't pass as all of the expect
need to be awaited. I tried to wrap fireEvent
s in act
as well, but according to few blog posts from Kent C. Dodds we shouldn't wrap fireEvent
in act
so although the test passes I still get the warnings.
Any ideas how I can fix this?
CodePudding user response:
I faced similar issues. Wrapping fireEvent with await waitFor
did the trick for me. So, when you call fireEvent.changeText
make sure to wrap it with await waitFor
In your case,
test.only("Sign in username field validates correctly", async () => {
... Other test suites
await waitFor(() => {
fireEvent.changeText(usernameInput, 'username');
});
await waitFor(() => {
fireEvent.changeText(usernameInput, 'toolongusernameshouldntbeallowed');
});
});
CodePudding user response:
Well, wrapping fireEvent
in act
actually solved the issue and I am not getting warnings, if has a different answer that would work or explanation why this work I would be delighted to hear it.