Updating Formik fields when testing with Jest and Enzyme or react-testing-library
Tags: testing, javascript, typescript, react
Published: 2020-05-07 08:00:31

Here is an example test suite using Jest and Enzyme but you could just as easily use another testing framework like react-testing-library.

*Please note: this example is heavily reliant on Formik version `1.5.8`. As of version `2.x.x`, Formik no longer uses class components under the hood and has switched to functional components.*

```ts
// Versions used:
react: 16.13.1
jest: 24.9.0
enzyme: 3.11.0
formik: 1.5.8
```

```tsx
// Helper function (entirely optional) - when mounting 
// components that use the store, theming etc we need 
// to wrap our components in their relevant providers
export const TestApp = ({ children, ...rest }: { children: React.ReactNode }) => {
  const store = configureStore();
  return (
    <Provider store={store}>
      <IntlProvider>
        <ThemeProvider theme={theme}>
          <BrowserRouter>
            {React.Children.map(children, (child) =>
              React.isValidElement(child) ? React.cloneElement(child, rest) : child
            )}
          </BrowserRouter>
        </ThemeProvider>
      </IntlProvider>
    </Provider>
  );
};

// Helper function - uses act() under the hood from react-dom/test-utils
const actImmediate = (wrapper: ReactWrapper) =>
  act(
    () =>
      new Promise<void>((resolve) => {
        setImmediate(() => {
          wrapper.update();
          resolve();
        });
      })
  );

// Helper function - sets Formik fields directly using it's instance
const setFormikFields = (wrapper: ReactWrapper, values: Record<string, string>) => {
  return new Promise((resolve) =>
    wrapper
      .find('Formik')
      .instance()
      .setState({ values }, () => {
        wrapper.update();
        resolve();
      })
  );
};

const updateLoginMock = jest.fn();

// data-qa tags for easy element selection within the component
const qa = {
  form: '[data-qa="form"]',
  passwordField: '[data-qa="passwordField"]',
  emailField: '[data-qa="emailField"]',
};

describe('Given a MyComponent component', () => {
  let wrapper: ReactWrapper;

  describe('When it is rendered', () => {
    beforeEach(() => {
      wrapper = mount(
        <TestApp>
          <MyComponent updateLogin={updateLoginMock} />
        </TestApp>
      );
    });

    describe('When the form fields receive a value', () => {
      const fields = {
        password: 'Pass123$',
        email: 'some@email.com',
      };

      beforeEach(async () => {
        await setFormikFields(wrapper, fields); // sets the form fields directly using Formik's instance. This happens asynchronously so we need to use async/await
      });

      it('Then the form fields should have the correct value', () => {
        expect(wrapper.find(qa.passwordField).prop('value')).toBe(fields.password);
        expect(wrapper.find(qa.emailField).prop('value')).toBe(fields.email);
      });

      describe('When the form is submitted (form is valid)', () => {
        beforeEach(async () => {
          wrapper.find(qa.form).simulate('submit');

          await actImmediate(wrapper); // uses act() under the hood from react-dom/test-utils
        });

        it('Then updateEmailMock should be called with the correct data', () => {
          expect(updateEmailMock).toHaveBeenCalledWith({ password: fields.password, email: fields.email });
        });
      });
    });
  });
});

```