Amit Uppal
Amit Uppal

Reputation: 193

React - How to mock useFormContext (react-hook-form)

I am using useFormContext in one the child component. this is the code for it:

const { register } = useFormContext<CustomerProfileFormData>();

How can I Mock useFormContext so that I can test child component. this is the test

it('should render properly', () => {
    render(<AddressDetails isEdit={false} />);
    expect(screen.getByTestId('address-details')).toBeInTheDocument();
});

I am getting this error TypeError: Cannot destructure property 'register' of '(0 , _reactHookForm.useFormContext)(...)' as it is null.Jest.

which makes sense since I did not mock useFormContext . How can I Mock it? any help will be appreciated.

Upvotes: 15

Views: 34471

Answers (6)

Ismael Torres
Ismael Torres

Reputation: 1

Just import renderHook from testing-library/react and use it like this:

import { render, renderHook, screen } from '@testing-library/react'
import { FormProvider, useForm } from 'react-hook-form'

import AddressForm from '../address-form'

const { result } = renderHook(() => useForm())

describe('some test', () => {
  it('Should render fields', () => {
    render(
      <FormProvider {...result.current}>
        <AddressForm />,
      </FormProvider>,
    )
expect(screen.getByTestId('someTestId')).toBeInTheDocument()
  })
})

Upvotes: 0

Here is another wrapper approach close to the one provided by @johannes.charra above. The function makes the wrapper easily reusable in multiple it tests.

import { render, screen } from 'rtl'
import { PropsWithChildren, ReactElement } from 'react'
import { FormProvider, useForm } from 'react-hook-form'

function renderWithFormProvider(ui: ReactElement): ReactElement {
  const Wrapper = ({ children }: PropsWithChildren<unknown>) => {
    const methods = useForm({
      defaultValues: {
        firstName: 'Bill',
      },
    })

    return <FormProvider {...methods}>{children}</FormProvider>
  }

  return render(<Wrapper>{ui}</Wrapper>)
}

it('should blablabla', () => {
  renderWithFormProvider(<MyComponent {...defaultProps} />)

  expect(screen.getByText('foo')).toBeInTheDocument()
})

Upvotes: 0

Matias Menker
Matias Menker

Reputation: 21

I found a solution that works for me:

jest.mock( "react-hook-form", () => ( {
  ...jest.requireActual( "react-hook-form" ),
  useFormContext: () => ( {
    handleSubmit: () => jest.fn(),
    getValues: () => jest.fn(),
  } ),
} ) );

Upvotes: 2

dawid debinski
dawid debinski

Reputation: 520

To mock react-hook-form v7 you can do as follow

jest.mock('react-hook-form', () => ({
  ...jest.requireActual('react-hook-form'),
  useFormContext: () => ({
    handleSubmit: () => jest.fn(),
    control: {
      register: jest.fn(),
      unregister: jest.fn(),
      getFieldState: jest.fn(),
      _names: {
        array: new Set('test'),
        mount: new Set('test'),
        unMount: new Set('test'),
        watch: new Set('test'),
        focus: 'test',
        watchAll: false,
      },
      _subjects: {
        watch: jest.fn(),
        array: jest.fn(),
        state: jest.fn(),
      },
      _getWatch: jest.fn(),
      _formValues: ['test'],
      _defaultValues: ['test'],
    },
    getValues: () => {
      return [];
    },
    setValue: () => jest.fn(),
    formState: () => jest.fn(),
    watch: () => jest.fn(),
  }),
  Controller: () => [],
  useSubscribe: () => ({
    r: { current: { subject: { subscribe: () => jest.fn() } } },
  }),
}));

Upvotes: 5

Johannes Charra
Johannes Charra

Reputation: 29923

You can either mock the context methods as indicated in other responses, or you can provide your component with an actual FormContext by creating an ad hoc wrapper component inside your test like this:

it('should do something', () => {
  const Wrapper = (props) => {
    const formMethods = useForm<CustomerProfileFormData>();

    return (
      <FormProvider {...formMethods}>
        {props.children}
      </FormProvider>
    );
  };

  render(
    <Wrapper>
      <AddressDetails />
    </Wrapper>
  );

  // your assertions here ... 
})

If you want to verify that your components behaves correctly upon form values, you could e.g. override the getValues method with preconfigured data.

const mockedGetValues = (key: string) => {
   // return test data for form key
}

return (
  <FormProvider {...formMethods} getValues={mockedGetValues}>
    {props.children}
  </FormProvider>
);

Upvotes: 19

goamn
goamn

Reputation: 2136

To make the useFormContext method work inside tests, RHF recommend wrapping it with <FormProvider> (Github-How to Test FormProvider / useFormContext).

Wrap your component with <FormProvider> and provide the required methods like this:

render(
  <FormProvider {...({ setValue: () => jest.fn() } as any)}>
    <AddressDetails isEdit={false} />
  </FormProvider>
);

Upvotes: 4

Related Questions