Description
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 SyntheticEvent
s, they are just simple Object
s. 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 Object
s, 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 await
s 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