Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v2] testing form with validationSchema #1543

Open
mfolnovic opened this issue May 23, 2019 · 20 comments
Open

[v2] testing form with validationSchema #1543

mfolnovic opened this issue May 23, 2019 · 20 comments

Comments

@mfolnovic
Copy link

🐛 Bug report

Current Behavior

I have a test that basically screenshots a form after removing focus from field that is required (while not filling in any value). For validations, I'm using Yup.

During fireEvent.blur(textField);, I get warning:

Warning: An update to Formik inside a test was not wrapped in act(...).

When testing, code that causes React state updates should be wrapped into act(...):

act(() => {
  /* fire events that update state */
});
/* assert on the output */

I'm also trying to screenshot page after blur, expecting to see validation message. From my experiments, I need to do: await waitForDomChange({ input }) to get correct screenshot.

I've also noticed there's no warning if I don't have validationSchema.

I assume this is connected to #1524 ?

Expected behavior

No warning.
No need for waitForDomChange.

Reproducible example

https://codesandbox.io/s/agitated-lalande-5nhh0

Your environment

Software Version(s)
Formik 2.0.1-rc.0
React 16.8.6
TypeScript 3.4.5
Browser Chrome
npm/Yarn Yarn
Operating System Linux
@mfolnovic
Copy link
Author

I'd like to add this also happens with validate (instead of validationSchema), e.g.:

const validate = (values) => {
  const errors = {};

  if (!values.test) {
    errors.test = "required";
  }

  return errors;
};

@mfolnovic
Copy link
Author

And I forgot to mention that, even surrounding fireEvent.blur with act doesn't help, e.g.:

act(() => {
  fireEvent.blue(input);
});

@mfolnovic mfolnovic changed the title testing form with validationSchema [v2] testing form with validationSchema May 23, 2019
@johnrom johnrom added the v2 label May 23, 2019
@cmelion
Copy link

cmelion commented May 28, 2019

I'm seeing the same issue when using a validation schema.

Warning: An update to Formik inside a test was not wrapped in act(...).

https://github.com/cmelion/react-cra-demo/blob/cfulnecky/formik/src/components/input-form/.test.js

@stale stale bot added the stale label Jul 27, 2019
@mayteio
Copy link

mayteio commented Jul 30, 2019

I am having the same issue when trying to change an input value:
fireEvent.change(getByLabelText(/email/i), {target: {value: fakeUsername}});

Obviously, wrapping this in act(() => {}) doesn't do anything to mitigate the problem.

@stale stale bot removed the stale label Jul 30, 2019
@stale stale bot added the stale label Sep 28, 2019
@2Steaks
Copy link

2Steaks commented Oct 29, 2019

Have you tried this?

await act(async() => {
    fireEvent.change(getByLabelText(/email/i), {target: {value: fakeUsername}});
});

@stale stale bot removed the stale label Oct 29, 2019
@duncanleung
Copy link
Contributor

duncanleung commented Nov 8, 2019

@2Steaks Thanks for that snippet. Works for me.

Looks like in https://github.com/jaredpalmer/formik/blob/master/packages/formik/src/Formik.tsx#L534-L553
setValues and setFieldValue both use the hook useEventCallback so you need to wait on the the promise.

const setValues = useEventCallback((values: Values) => {
    dispatch({ type: 'SET_VALUES', payload: values });
    return validateOnChange
      ? validateFormWithLowPriority(state.values)
      : Promise.resolve();

@pietmichal
Copy link

pietmichal commented Dec 3, 2019

Including this information in the documentation would be very helpful.

Edit: Wouldn't using wait utility fix the issue? https://testing-library.com/docs/dom-testing-library/api-async#wait

@pleunv
Copy link

pleunv commented Oct 1, 2020

Running into this while attempting to upgrade from v1 to v2, and having it cause warnings in hundreds of tests. I'd like to avoid having to wrap all of these with async act calls if I can help it... Did anyone find any other workaround? Using @testing-library/react and @testing-library/user-event fwiw.

@cakecatz
Copy link

@pleunv I use the same libraries, and I have written my test like this. I hope this will help.

const onSubmit = jest.fn();

render(<Form onSubmit={onSubmit} />);

// without act()
userEvent.click(screen.getByRole('button', { name: 'foo' });

await waitFor(() => expect(onSubmit).toHaveBeenCalledWith({ /** XXX */ }));

@burrack
Copy link

burrack commented Nov 13, 2020

Did anyone was able to fix it using Enzyme?
Does that warning indicate that there is an issue in the website? or is it a warning saying you are doing something wrong in your test?

@kettanaito
Copy link

kettanaito commented Dec 2, 2020

After numerous attempts this is what worked for me:

import { render, fireEvent, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'

test('validates the email field', async () => {
  const { getByText, getByPlaceholderText } = render(<Form />)
  const emailInput = getByPlaceholderText('Email')
  
  userEvent.type(emailInput, 'invalidemail.com')
  fireEvent.blur(emailInput)
  
  await waitFor(() => {
    expect(getByText(/Please enter a valid email/i)).toBeInTheDocument()
  })
})
  • Use userEvent.type to type into the input.
  • Don't wrap anything in act.
  • Wrap the assertions in await waitFor block (waitFor is the new API replacing the previous wait).

Edit: you can also use await findByText() which is the same thing as await waitFor with getByText.

@jamiehaywood
Copy link

I wanted to add to this for anyone that is still struggling.

I had a beforeEach render, so I had to wrap that render.

let wrapper: RenderResult

beforeEach(() => {
  wrapper = renderWithProviders(<ManualAddressForm {...props} />)
})

@tyteen4a03
Copy link

tyteen4a03 commented Feb 7, 2022

@burrack No solutions for enzyme at the moment I don't think. This breaks testing for us so hope it's fixed soon.

@MejanH
Copy link

MejanH commented Mar 5, 2022

This issue was fixed for me by updating formik and testing library packages to the latest versions.

[email protected] and 👇

image

theborakompanioni added a commit to joinmarket-webui/jam that referenced this issue Apr 6, 2022
theborakompanioni added a commit to joinmarket-webui/jam that referenced this issue Apr 6, 2022
ghost pushed a commit to joinmarket-webui/jam that referenced this issue Apr 7, 2022
* feat: add password confirmation on create screen

* fix: labels and feedback in create form according to figma

* test: verify password confirm validation message

* fix: remove unnecessary escape character

* fix: do not escape '-'

* refactor: comments and better name for escape function

* deps: add Formik form validation library

* refactor: use Formik for CreateWallet form validation

* refactor: remove 'isCreating' flag in favor of 'onFinished' callback

* test: adapt tests to be compatible with Formik

see jaredpalmer/formik#1543
@kahlan88
Copy link

Form 

Doesn't work with more up to date versions for me:

"@testing-library/jest-dom": "^5.16.5",
    "@testing-library/react": "^13.4.0",
"formik": "^2.2.9",

@romulof
Copy link

romulof commented Oct 10, 2023

Have you tried this?

await act(async() => {
    fireEvent.change(getByLabelText(/email/i), {target: {value: fakeUsername}});
});

I think this solution deserves a bit more explanation.

Normally we don't need to wrap fireEvent calls in act(), because testing-library takes care of that (I think), but in this case fireEvent.change() triggers form validation, which is async, so state is only updated when that promises resolves which is probably out of the scope where fireEvent was expecting state to change.

In this case we need to wrap 2 things in act(): user action and awaiting promise flush. So I propose the following fix instead:

fireEvent.change(getByLabelText(/email/i), { target: { value: fakeUsername } });
await act(() => Promise.resolve()); // Flush microtasks used by Formik validation

As alternative if you are using delays/timers:

// Flush timers and microtasks used by Formik validation
await act(() => jest.runAllTimersAsync());

@vicasas
Copy link

vicasas commented Jul 24, 2024

Any news or update regarding this error? We are having the same problem using fireEvent.change

it('should submit the form with entered values', async () => {
    const alertMock = jest.spyOn(window, 'alert').mockImplementation(() => {})

    render(<SignInForm />)

    const usernameInput = screen.getByLabelText(/username/i)
    const passwordInput = screen.getByLabelText(/password/i)
    const submitButton = screen.getByRole('button', { name: /signin/ })

    fireEvent.change(usernameInput, { target: { value: 'testuser' } })
    fireEvent.change(passwordInput, { target: { value: 'password123' } }

    fireEvent.submit(submitButton)

    expect(alertMock).toHaveBeenCalledWith(
      JSON.stringify(
        {
          username: 'testuser',
          password: 'password123',
          branch: 1,
        },
        null,
        2,
      ),
    )

    alertMock.mockRestore()
  })

For now we have used the following solution from @romulof:

fireEvent.change(getByLabelText(/email/i), { target: { value: fakeUsername } });
await act(() => Promise.resolve()); // Flush microtasks used by Formik validation

@tyteen4a03
Copy link

Any news or update regarding this error? We are having the same problem using fireEvent.change

it('should submit the form with entered values', async () => {
    const alertMock = jest.spyOn(window, 'alert').mockImplementation(() => {})

    render(<SignInForm />)

    const usernameInput = screen.getByLabelText(/username/i)
    const passwordInput = screen.getByLabelText(/password/i)
    const submitButton = screen.getByRole('button', { name: /signin/ })

    fireEvent.change(usernameInput, { target: { value: 'testuser' } })
    fireEvent.change(passwordInput, { target: { value: 'password123' } }

    fireEvent.submit(submitButton)

    expect(alertMock).toHaveBeenCalledWith(
      JSON.stringify(
        {
          username: 'testuser',
          password: 'password123',
          branch: 1,
        },
        null,
        2,
      ),
    )

    alertMock.mockRestore()
  })

For now we have used the following solution from @romulof:

fireEvent.change(getByLabelText(/email/i), { target: { value: fakeUsername } });
await act(() => Promise.resolve()); // Flush microtasks used by Formik validation

Formik is essentially unmaintained. Migrate to react-hook-forms.

@vicasas
Copy link

vicasas commented Jul 24, 2024

@tyteen4a03 Where does it say you have to migrate? Is there any collaborator, contributor or maintainer who indicates this?

@tyteen4a03
Copy link

@tyteen4a03 Where does it say you have to migrate? Is there any collaborator, contributor or maintainer who indicates this?

Nothing, but the huge amount of unresolved issues, unmerged PRs and the frequency of release within the past 12 months are strong indicators.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests