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

2

15

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

Witherspoon answered 18/4, 2022 at 20:23 Comment(0)
P
18

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`
Peanut answered 20/5, 2022 at 3:55 Comment(1)
This solved my (slightly different) problem! I'm going to post an answer with my 'version'.Goodell
G
3

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.

Goodell answered 19/10, 2022 at 14:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.