Answer a question

TL;DR

How to write unit test with Jest & Enzyme for components with 'useField' hook? On shallow render I get this error

    Warning: Formik context is undefined, please verify you are calling 
useFormikContext() as child of a <Formik> component
TypeError: Cannot read property 'getFieldProps' of undefined

Details

The project build with

  1. React
  2. TypeScript
  3. Formik
  4. Material UI
  5. Jest Enzyme

It's a learning project, so I am playing around with different approaches. That's why I've put all components in different files thought it might not be necessary.

Structure:

Formik.tsx
  |
  | AddContactForm.tsx
    |
    | TextInput.tsx
    | TextInput.test.tsx

Details:

Formik.tsx Just a wrapper where we have all properties of the form

 <Formik
            initialValues={initialValues}
            validationSchema={...}
            onSubmit={...};
            component={AddContactForm}
          />

AddContactForm.tsx Here I am passing field meta and props to input. It seems to be not the best solution, I'd like to use useField() hook inside a component itself

<Form>
        <TextInput
          label="First Name"
          name={"firstName"}
          placeholder="Jane"
          field={getFieldProps("firstName")}
          meta={getFieldMeta("firstName")}
        />
        <button type="submit">Submit</button>
    </Form>

TextInput.tsx This is current solution - I can write unit tests for it - for example snapshot testing.

const TextInput: React.FC<MyInput> = React.memo(
  ({ label, field, meta}: MyInput) => {
    return (
      <>
        <TextField
          label={label}
          type="text"
          {...field}
          error={meta?.touched && meta?.error ? true : undefined}
          helperText={meta?.touched ? meta?.error : undefined}
        />
      </>
    );
  }
);

TextInput.test.tsx Here I have to write big mockProps object to mock all things :(

describe("<TextInput/>", () => {
  it("Match Snapshot", () => {
    const mockProps: MyInput = {
      label: "label",
      name: "name",
      placeholder: "placeholder",
      meta: {
        touched: false,
        error: "",
        initialError: "",
        initialTouched: false,
        initialValue: "",
        value: "",
      },
      field: {
        value: "",
        checked: false,
        onChange: jest.fn(),
        onBlur: jest.fn(),
        multiple: undefined,
        name: "firstName",
      },
    };

    expect(
      shallow(
        <TextInput
          label="label"
          name="name"
          placeholder="placeholder"
          {...mockProps.meta}
          {...mockProps.field}
        />
      )
    ).toMatchSnapshot();
  });
});

Instead, what I want is to get field and meta not by props, but with useField() hook.

TextField.tsx

const TextInput: React.FC<MyInput> = React.memo(
  ({ label, ...props }: MyInput) => {
    const [field, meta] = useField(props);
    return (
      <>
        <TextField
          label={label}
          type="text"
          {...field}
          {...props}
          error={meta?.touched && meta?.error ? true : undefined}
          helperText={meta?.touched ? meta?.error : undefined}
        />
      </>
    );
  }
);

But then I have not clue how to write a test for it. It seems like it wants to have Formik context inside a test, but it's not possible to use useFormikContext() hook in test file as it violates rules of hooks usage.

Answers

To learn more about Mocking in Jest, you should read the official documentation on:

  • ES6 Class Mocks
  • Mock Functions
  • Manual Mocks

You can also look at Enzyme's ShallowWrapper API documentation

// TextInput.test.tsx
import React from 'react';
import { useField } from 'formik'; // package will be auto mocked
import TextInput from '...';

jest.mock('formik'); // formik package is auto mocked

describe("<TextInput/>", () => {
  it("Match Snapshot", () => {
    const mockMeta = {
      touched: false,
      error: "",
      initialError: "",
      initialTouched: false,
      initialValue: "",
      value: "",
    }
    const mockField = {
      value: "",
      checked: false,
      onChange: jest.fn(),
      onBlur: jest.fn(),
      multiple: undefined,
      name: "firstName",
    };
    useField.mockReturnValue([mockField, mockMeta]);
    
    const mockProps = {...};
    expect(
      shallow(<TextInput {...mockProps} />).debug()
    ).toMatchSnapshot();
  });
});
// TextInput.tsx
import React from 'react';
import { useField } from 'formik';
import ...

const TextInput: React.FC<MyInput> = React.memo(
  ({ label, ...props }: MyInput) => {
    const [field, meta] = useField(props);
    return (
      <>
        <TextField
          label={label}
          type="text"
          {...field}
          {...props}
          error={meta?.touched && meta?.error ? true : undefined}
          helperText={meta?.touched ? meta?.error : undefined}
        />
      </>
    );
  }
);
Logo

React社区为您提供最前沿的新闻资讯和知识内容

更多推荐