Skip to content

Formik, React Native, undefined events, and timeouts awaiting async act. #1530

Closed
@ahwatts

Description

@ahwatts

Describe the bug

I have two issues, both of which are illustrated in this repository here: https://github.com/ahwatts/rntlFormikTest

The test suite in question is here: https://github.com/ahwatts/rntlFormikTest/blob/main/__tests__/TestForm.test.tsx

If you clone that repository, do a yarn install, and then run a yarn test on it, you'll get two test failures:

andrew@dr-bernice rntlFormikTest [rntlFormikTest:main =] % yarn test
yarn run v1.22.21
$ jest
 FAIL  __tests__/TestForm.test.tsx (5.488 s)
  ● enter form email 1

    TypeError: Cannot read properties of undefined (reading 'name')

      at name (node_modules/formik/src/Formik.tsx:690:15)
      at executeBlur (node_modules/formik/src/Formik.tsx:709:25)
      at handler (node_modules/@testing-library/react-native/src/user-event/utils/dispatch-event.ts:23:5)
      at callback (node_modules/@testing-library/react-native/src/act.ts:31:24)
      at act (node_modules/react/cjs/react.development.js:2512:16)
      at actImplementation (node_modules/@testing-library/react-native/src/act.ts:30:25)
      at dispatchEvent (node_modules/@testing-library/react-native/src/user-event/utils/dispatch-event.ts:22:11)
      at Object.type (node_modules/@testing-library/react-native/src/user-event/type/type.ts:78:16)
      at wrapAsync (node_modules/@testing-library/react-native/src/helpers/wrap-async.ts:22:22)

  ● enter form email 2

    thrown: "Exceeded timeout of 5000 ms for a test.
    Add a timeout value to this test to increase the timeout, if this is a long-running test. See https://jestjs.io/docs/api#testname-fn-timeout."

      33 | });
      34 |
    > 35 | test('enter form email 2', async () => {
         |     ^
      36 |   let email = '';
      37 |   function handleChangeEmail(text: string) {
      38 |     email = text;

      at Object.<anonymous> (__tests__/TestForm.test.tsx:35:5)

 PASS  __tests__/App.test.tsx

Test Suites: 1 failed, 1 passed, 2 total
Tests:       2 failed, 3 passed, 5 total
Snapshots:   1 passed, 1 total
Time:        5.604 s
Ran all test suites.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

First failure

The first failure, in "enter form email 1", I'm attempting to use the userEvent.type() event generator to enter an email address in to a TextInput:

test('enter form email 1', async () => {
  let email = '';
  function handleChangeEmail(text: string) {
    email = text;
  }

  render(<TestForm onChangeEmail={handleChangeEmail} />);
  const user = userEvent.setup();
  await user.type(screen.getByPlaceholderText('Email'), 'test@example.com');
  expect(email).toEqual('test@example.com');
});

The failure is occurring here:

https://github.com/jaredpalmer/formik/blob/main/packages/formik/src/Formik.tsx#L690

and it appears that Formik is expecting the events (in this case, specifically the blur event) that it handles to be the native events, but while the events generated by RNTL are structurally the same as SyntheticEvents, they are just simple Objects. In the non-testing case this appears to work because SyntheticEvent seems to have some kind of magic delegating things like target to the nativeEvent member, but since these are Objects, they don't have this magic, and so the destructuring fails.

Second failure

The second failure is a timeout. Since userEvent.type() wasn't working, I then tried to use fireEvent.changeText():

test('enter form email 2', async () => {
  let email = '';
  function handleChangeEmail(text: string) {
    email = text;
  }
  let wasValidating = false;
  let resolve = null;
  const promise = new Promise((res, _rej) => {
    resolve = res;
  });
  function handleFormStateChange(isValidating: boolean) {
    if (!wasValidating && isValidating) {
      wasValidating = true;
    } else if (wasValidating && !isValidating) {
      wasValidating = false;
      resolve!();
    }
  }

  render(
    <TestForm
      onChangeEmail={handleChangeEmail}
      onFormStateChange={handleFormStateChange}
    />,
  );
  fireEvent.changeText(
    screen.getByPlaceholderText('Email'),
    'test@example.com',
  );
  await act(async () => {
    await promise;
  }); // .then(() => {});
  expect(email).toEqual('test@example.com');
});

Since Formik runs its validation asynchronously, I had to set up a promise to wait for the validation to finish in order to avoid the Warning: An update to Formik inside a test was not wrapped in act(...). error. So after the fireEvent call, we await an asynchronous act() call, which awaits the Promise which is resolved when the validation completes. It appears, however, that the Promise returned by act() never resolves. If you play around with it, you'll see that promise does resolve, but the act() does not. However, if I attach a .then() with a no-op handler to the act() promise (as is commented out there), the whole thing will resolve properly and the test will pass.

Expected behavior

I expected one or both of these to pass, either as written or with a modification related to validation (with the first failure), or without the no-op then() clause (on the second failure).

Steps to Reproduce

I stripped these down to a simple repo here: https://github.com/ahwatts/rntlFormikTest:

$ git clone https://github.com/ahwatts/rntlFormikTest
$ yarn install
$ yarn test

Versions

andrew@dr-bernice rntlFormikTest [rntlFormikTest:main =] % npx envinfo --npmPackages react,react-native,react-test-renderer,@testing-library/react-native,formik

  npmPackages:
    @testing-library/react-native: ^12.4.0 => 12.4.0
    formik: ^2.4.5 => 2.4.5
    react: 18.2.0 => 18.2.0
    react-native: 0.72.7 => 0.72.7
    react-test-renderer: 18.2.0 => 18.2.0
andrew@dr-bernice rntlFormikTest [rntlFormikTest:main =] % node --version
v21.2.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions