bojackhorseman99
bojackhorseman99

Reputation: 329

Yup and Formik not validating form when testing with Jest and React Testing Library

I recently updated Jest to it's latest 28. version and my unit tests stopped working, particularly on tests that validated my form with Yup and Formik and showed the error messages. If I rollback the Jest version it does indeed work fine.

Here is my package.json dependencies

"dependencies": {
    "@chakra-ui/icons": "^1.0.16",
    "@chakra-ui/react": "^1.6.10",
    "@emotion/react": "^11.5.0",
    "@emotion/styled": "^11.3.0",
    "@types/styled-components": "^5.1.15",
    "axios": "^0.24.0",
    "formik": "^2.2.9",
    "framer-motion": "^4.1.17",
    "graphql": "^16.3.0",
    "graphql-request": "^4.0.0",
    "next": "^12.2.0",
    "next-i18next": "^10.2.0",
    "nookies": "^2.5.2",
    "react": "17.0.2",
    "react-dom": "17.0.2",
    "react-icons": "^4.3.1",
    "react-number-format": "^4.9.3",
    "react-query": "^3.34.6",
    "styled-components": "^5.3.3",
    "yup": "^0.32.11"
  },
  "devDependencies": {
    "@testing-library/dom": "^8.16.0",
    "@testing-library/jest-dom": "^5.16.4",
    "@testing-library/react": "^12.1.5",
    "@testing-library/react-hooks": "^7.0.2",
    "@testing-library/user-event": "^13.5.0",
    "@types/node": "18.0.5",
    "@types/react": "17.0.33",
    "eslint": "7.32.0",
    "eslint-config-next": "12.0.1",
    "eslint-config-prettier": "^8.3.0",
    "jest": "^28.1.3",
    "jest-environment-jsdom": "^28.1.3",
    "msw": "^0.39.2",
    "react-test-renderer": "^17.0.2",
    "typescript": "4.4.4"
  }

Here is my jest.config.js

// jest.config.js
const nextJest = require('next/jest');

const createJestConfig = nextJest({
  // Provide the path to your Next.js app to load next.config.js and .env files in your test environment
  dir: './',
});

// Add any custom config to be passed to Jest
const customJestConfig = {
  setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
  // if using TypeScript with a baseUrl set to the root directory then you need the below for alias' to work
  moduleNameMapper: {
    // Handle module aliases (this will be automatically configured for you soon)
    '^@/components/(.*)$': '<rootDir>/components/$1',

    '^@/pages/(.*)$': '<rootDir>/pages/$1',
  },
  moduleDirectories: ['node_modules', '<rootDir>/'],
  testEnvironment: 'jsdom',
};

// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
module.exports = createJestConfig(customJestConfig);

Here is my failling test


  it('validates empty form values', async () => {
    server.use(
      graphql.query('GetAllProducts', (req, res, ctx) => {
        return res(ctx.status(200), ctx.data(getProductsResponseBody));
      })
    );

    const signupResult = renderWithDefaultClient(<ProductPage />);

    await userEvent.click(
      await signupResult.findByText('addProductButtonLabel')
    );

    await userEvent.click(await signupResult.findByText('createButtonLabel'));

    expect(await signupResult.findByText('required')).toBeInTheDocument();
  });

And here is the component that I'm testing

import {
  Button,
  FormControl,
  FormErrorMessage,
  FormLabel,
  HStack,
  Input
} from '@chakra-ui/react';
import { Field, Form, Formik } from 'formik';
import { useTranslation } from 'next-i18next';
import React from 'react';
import * as Yup from 'yup';
import {
  FormProduct,
  IProduct
} from '../../interfaces/pages/product/ProductInterface';
import { returnEmptyStringIfNull } from '../../utils/StringUtils';
  
  const ProductForm = () => {
    const { t } = useTranslation(['product', 'shared']);
  
    const ProductFormSchema = Yup.object().shape({
      code: Yup.string().max(45, t('characterLimitFortyFive', { ns: 'shared' })),
      name: Yup.string()
        .max(45, t('characterLimitFortyFive', { ns: 'shared' }))
        .required(t('required', { ns: 'shared' })),
    });
  
    const initialValues: FormProduct = {
      code: returnEmptyStringIfNull(product.code),
      name: returnEmptyStringIfNull(product.name),
    };
  
    const onSubmitForm = (productForm: FormProduct) => {
        createMutation.mutate(productForm);
    };
  
    return (
      <>
        <Formik
          initialValues={initialValues}
          validationSchema={ProductFormSchema}
          onSubmit={(form) => {
            onSubmitForm(form);
          }}
        >
          {(props) => (
            <Form data-testid='productForm' style={{ marginTop: '20px' }}>
              <HStack spacing={'24px'} w={'2xl'} mb={3}>
                <Field name='code'>
                  {
                    // @ts-ignore
                    ({ field, form }) => (
                      <FormControl
                        isInvalid={form.errors.code && form.touched.code}
                      >
                        <FormLabel htmlFor='code'>{t('codeLabel')}</FormLabel>
                        <Input {...field} id='code' data-testid='code' />
                        <FormErrorMessage>{form.errors.code}</FormErrorMessage>
                      </FormControl>
                    )
                  }
                </Field>
                <Field name='name'>
                  {
                    // @ts-ignore
                    ({ field, form }) => {
                      return (
                        <FormControl
                          isInvalid={form.errors.name && form.touched.name}
                          isRequired
                        >
                          <FormLabel htmlFor='name'>{t('nameLabel')}</FormLabel>
                          <Input {...field} id='name' data-testid='name' />
                          <FormErrorMessage>{form.errors.name}</FormErrorMessage>
                        </FormControl>
                      );
                    }
                  }
                </Field>
              </HStack>
              <HStack spacing={'24px'} mt={4}>
                <Button colorScheme='blue' type='submit'>
                  {t('createButtonLabel', { ns: 'shared' })}
                </Button>
              </HStack>
            </Form>
          )}
        </Formik>
      </>
    );
  };
  
  export default ProductForm;
  

As you can see I'm trying to validate that the required error message is indeed getting shown, but after I update jest and install jsdom it doesn't work.

It works again by installing Jest 27.5.1, all of my failling tests are due to errors by formik and yup not being shown.

Upvotes: 0

Views: 2777

Answers (2)

havocheavy
havocheavy

Reputation: 120

Doing a bit of searching I found this GitHub issue: https://github.com/jaredpalmer/formik/issues/544

Try adding noValidate to your form component. I got it to pass when making that change.

Upvotes: 1

bojackhorseman99
bojackhorseman99

Reputation: 329

Apparently JSDOM is now validating HTML5 form inputs when it has the required attribute set, after I took out the "isRequired" on ChakraUI it worked, even though that it's what's expected in a real browser environment but previous versions like the one that I was using was not validating the form as it should have.

A reference to JSDOM can be seen here: https://github.com/jsdom/jsdom/issues/2898

Upvotes: 0

Related Questions