Vuk
Vuk

Reputation: 873

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

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,
    });
  }
}

Upvotes: 8

Views: 15988

Answers (2)

Emad Armoun
Emad Armoun

Reputation: 2087

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.

Upvotes: 2

Vuk
Vuk

Reputation: 873

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,
  });
};

Upvotes: 7

Related Questions