Charlie Maloney
Charlie Maloney

Reputation: 151

How to mock a react function component that takes a ref prop?

The Problem:

I am trying to use jest and React Testing Library to mock a functional component that is wrapped in React.ForwardRef(), but I keep getting this warning (which is failing my test):

Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

Here is the component I want to test:

const ParentComponent = () => {
  const childRef = useRef(null);

  return (
    <div data-testid="parent">
      Parent
      {/* want to mock this ChildComponent */}
      <ChildComponent ref={childRef} />
    </div>
  );
};

Here is the component I want to mock:

const ChildComponent = forwardRef((props, ref) => (
  <div ref={ref} {...props}>
    Child
  </div>
));

What I've tried:


jest.mock("../ChildComponent", () => ({
  __esModule: true,
  default: () => <div>Mock Child</div>
}));

result: Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?


jest.mock('../ChildComponent', () => {
  const { forwardRef } = jest.requireActual('react');
  return {
    __esModule: true,
    default: () => forwardRef((props, ref) => <div ref={ref} />),
  };
});

result: Objects are not valid as a React child (found: object with keys {$$typeof, render})

Upvotes: 15

Views: 13047

Answers (2)

AncientSwordRage
AncientSwordRage

Reputation: 7598

I had a similar issue to Ryan S, and used their answer with a slight modification.

In my problem, I had to test with enzyme that the exact child component was rendered, but without using enzyme's mount only shallow:

it('renders the child', () => {
  expect(wrapper.exists(ChildComponent)).toBe(true);
})

My modification of the solution is thus:

jest.mock('../ChildComponent', () => {
  const { forwardRef } = jest.requireActual('react');
  const ChildComponentShim = jest.requireActual('../ChildComponent');
  return {
    __esModule: true,
    default: forwardRef((props, ref) => <ChildComponentShim { ...props } ref={ref} />),
  };
});

Which is merely to let the actual child component to be rendered, but makes sure the ref from forwardRef is used without mounting the parent or child component.

Upvotes: 3

Ryan S
Ryan S

Reputation: 201

Sweet, I just had this exact same issue and your last attempt where you imported forwardRef within the mock function helped me figure this out. I had luck with this:

jest.mock('../../../client/components/visualizations/Visualizer', () => {
  const { forwardRef } = jest.requireActual('react');
  return {
    __esModule: true,
    default: forwardRef(() => <div></div>),
  };
});

So using your example from above, I believe this should work:

jest.mock('../ChildComponent', () => {
  const { forwardRef } = jest.requireActual('react');
  return {
    __esModule: true,
-   default: () => forwardRef((props, ref) => <div ref={ref} />),
+   default: forwardRef((props, ref) => <div ref={ref} />),
  };
});

Also including my own attempt below with the resulting error message in case it helps someone else find this post in the future:

jest.mock('../ChildComponent', () => ({
  __esModule: true,
  default: React.forwardRef(() => <div></div>),
}));

Resulted in error:

The module factory of `jest.mock()` is not allowed to reference any out-of-scope variables.
    Invalid variable access: React`

Upvotes: 19

Related Questions