Home > other >  Jest Formik - Warning: You called act(async () => ...) without await
Jest Formik - Warning: You called act(async () => ...) without await

Time:02-21

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 fireEvents 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.

  • Related