How to mock a React.MemoExoticComponent (React.memo) with jest using typescript

Viewed 485

In my project, I am using React + typescript and jest + Testing-Library/react for testing. I am trying to mock a functional component wrapped in React.memo. With "normal" components I take this approach:

jest.mock('../SearchComponent');

const mockSearchComponent = SearchComponent as jest.MockedFunction<
    typeof SearchComponent
>;

which then allows me to mock different implementations, e.g.

    mockSearchComponent.mockImplementation((props: Props) => {
        /* do something with props */
        return <div />;
    });

When attempting to do the same with a functional component wrapped in React.memo:

jest.mock('../PaginationComponent');
const mockPaginationComponent = PaginationComponent as jest.MockedFunction<
    typeof PaginationComponent
>;

mockPaginationComponent.mockImplementation(
        (props: PaginationComponentProps) => {
            return <div />;
        }
    );

VSCodium does not seem to find any problem with it, hovering over mockPaginationComponent, it shows

const mockPaginationComponent: jest.MockedFunction<React.MemoExoticComponent<({ start, rows, toNumber, totalNumber, onPaginationUpdate, }: {
    start: number;
    rows: number;
    toNumber: number;
    totalNumber: number;
    onPaginationUpdate(start: number, rows: number): void;
}) => JSX.Element>>

However, when I run jest, I get the error

TypeError: mockPaginationComponent.mockImplementation is not a function

      88 |      };
      89 |
    > 90 |      mockPaginationComponent.mockImplementation(
         |                              ^
      91 |              (props: PaginationComponentProps) => {
      92 |                      return <div />;
      93 |              }

And when I remove the mockImplementation and instead try to e.g. expect a call to the mockPaginationComponent, such as:

renderWithRouter(<PersonSearch />);

            expect(mockPaginationComponent).not.toHaveBeenCalled();

I get the error:

    expect(received).not.toHaveBeenCalled()

    Matcher error: received value must be a mock or spy function

    Received has type:  object
    Received has value: {"$$typeof": Symbol(react.memo), "compare": null, "type": [Function mockConstructor]}

      261 |                     renderWithRouter(<PersonSearch />);
      262 |
    > 263 |                     expect(mockPaginationComponent).not.toHaveBeenCalled();
          |                                                         ^
      264 |

Am I missing something in my jest config? Do I have to add another test layer to kindof mock the React.memo as well? I would have expected to be able mock the component wrapped in React.memo just like a "normal" component.

1 Answers

I encountered the same issue, and I resolved it by exporting the component and the memo separately.

export const MyComponent: React.FC<MyProps> = (props: MyProps) => {};
export const MemoizedMyComponent = React.memo(MyComponent, compareFn)

in the implementation, use the memoized version

return (
<div><MemoizedMyComponent props={props}/></div>
)

In the test, mock your non-memoized function as per usual and your test will simply memoize your mocked function instead.

Related