How to mock the formik useFormikContext hook when writing unit tests with jest
Asked Answered
C

2

8

I have a simple component see below, that basically attempts to grab some data from the formik FormContext using the useFormikContext hook.

However when attempting to write unit tests for this component it wants me to mock the hook which is fine, however, mocking the hook with typescript means returning well over 20 properties most of which are a variety of methods and functions.

Has anyone found a better way of doing this? Just seems a bit annoying even if I get it to work as I only need 1 field from the hook.

Component

const AphControlInput: React.FC<IAphControlInput> = ({ name, value, label, type, small, disabled, vertical = true, className = '', ...attributeOptions }) => {
  const form = useFormikContext();

  return (
    <>
      <div className={
        `aph-control-input ${className}` +
        `${small ? ' aph-control-input--small' : ''}` +
        `${vertical ? ' aph-control-input--block' : ''}` +
        `${form.getFieldMeta(name).error ? ' aph-control-input--invalid' : ''}`
      }
      >
        <Field
          className='aph-control-input__input'
          name={name}
          id={value ? value?.toString() : name}
          type={type}
          value={value ? value : undefined}
          disabled={disabled}
          {...attributeOptions}
        />
        <label className='aph-control-input__text' htmlFor={value ? value?.toString() : name}>
          {label}
        </label>
      </div>
    </>
  );
};

Unit test (Incomplete this was just a quick attempt at mocking all the returns for the hook)

describe('AphInputLabel UnitTests', () => {
  let wrapper: any;
  const useFormikContextMock = jest.spyOn(formik, 'useFormikContext');

  beforeEach(() => {
    useFormikContextMock.mockReturnValue({
      values: { testName: 'testValue' },
      getFieldMeta: getFieldMetaMock,
      touched: true,
      isSubmitting: false,
      isValidating: false,
      errors: false,
      submitCount: 0,
      setStatus: (status?: any) => { return null },
      setErrors: (errors?: FormikErrors<any>) => { return null },
      setSubmitting: (isSubmitting: boolean) => { return null },
      setTouched: (touched: FormikTouched<any>, shouldValidate?: boolean) => { return null },
      setValues: (values: React.SetStateAction<any>, shouldValidate?: boolean) => { return null },
      setFieldValue: (field: string, value: any, shouldValidate?: boolean) => { return null },
      setFieldError: (field: string, message: string | undefined) => { return null },
      setFieldTouched: (field: string, isTouched?: boolean, shouldValidate?: boolean) => { return null },
      resetForm: (nextState?: Partial<FormikState<any>>) => { return null },
      validateField: (field: string) => { return null },
      setFormikState: (f: FormikState<any> | ((prevState: FormikState<any>) => FormikState<any>), cb?: () => void) => null,
      validateForm: (values?: any) => { return new Promise<FormikErrors<unknown>>((resolve, reject) => {
        const formikErrors: FormikErrors<any> = {
          'testName': ''
        }
          return formikErrors;
        });
      },
      submitForm: () => new Promise<void>(() => null),
      handleSubmit: (e?: React.FormEvent<HTMLFormElement> | undefined) => null,
      handleReset: (e?: React.SyntheticEvent<any>) => null,
    });
  }
}

Christiachristian answered 12/11, 2021 at 0:22 Comment(3)
How did you import formik on this line : const useFormikContextMock = jest.spyOn(formik, 'useFormikContext');Corrinecorrinne
Where is getFieldMetaMock as well please?Corrinecorrinne
I have updated the answer to hopefully provide more detail let me know if that helps.Christiachristian
C
7

I resolved this issue not 100% sure it is the best solution but have posted here in case it helps anyone with a similar issue.

I basically overwrote the FormikType allowing me to ignore all of the fields and methods I wasn't using, it clearly has some drawbacks as it is removing the type-safety, but I figured since it was only inside the unit test it is somewhat okay.

Import

import * as Formik from 'formik';

Test setup

describe('AphControlInput: UnitTests', () => {
  let wrapper: any;
  const useFormikContextMock = jest.spyOn(Formik, 'useFormikContext');

  beforeEach(() => {
    useFormikContextMock.mockReturnValue({
      getFieldMeta: getFieldMetaMock
    } as unknown as any);

    wrapper = shallow(
      <AphTextInput {...baseComponentProps} />
    );
  });
}

Helper method

export const getFieldMetaMock = (name: string) => {
  return ({
    value: 'testValue',
    initialTouched: true,
    touched: false,
  });
};
Christiachristian answered 15/11, 2021 at 0:24 Comment(6)
Would mind to share more details , I need help on this issue, I added questions on your postCorrinecorrinne
Thanks , please also add this useFormikContextMock declarationCorrinecorrinne
Because here formik has a small letter f : const useFormikContextMock = jest.spyOn(formik, 'useFormikContext'); How is it imported ?Corrinecorrinne
I have updated to include the const declarationsChristiachristian
Thanks so much, let me test againCorrinecorrinne
It worked, thanksCorrinecorrinne
A
3

Also, you can mock the Formik library & its desired functions, in this way:

jest.mock('formik', () => ({
  useFormikContext: jest.fn().mockReturnValue({
    getFieldMeta: jest.fn(),
  }),
}));

Just put this code at the top of your unit test file, outside of the describe scope.

Augsburg answered 23/2, 2023 at 13:57 Comment(1)
Great answer but it won't work if you are destructuring useFormikContext in your code. In this case, omit the jest.fn().mockReturnValue and just return the object directlyEldred

© 2022 - 2024 — McMap. All rights reserved.