reactjsjestjsreact-testing-librarymemoization

Test that a memoized component is not rerendered


In my last commit, I degraded the performance of my React app (I introduced Context, and I learned that it interfers with Memoization).

I would like to add tests that checks that a memoized component is not rerenderer after a user action.

App.jsx

import {MemoizedChildComponent} from "./ChildComponent"

export function App() {
 return (
  <div>
   ...
   <MemoizedChildComponent prop1={...} prop2../>
   ...
  </div>
 )
}

ChildComponent.jsx

import {memo} from "react"

function ChildComponent({prop1, prop2,...}) {
 return (
   ...
 )
}

const MemoizedChildComponent = memo(ChildComponent);

export { MemoizedChildComponent};

Using Jest & React Testing Library, I would like to write this test :

import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { App } from "./App"

test("ChildComponent was not rerendered", async () => {
  render(<App>);
  await userEvent.click(...)

  // Assert that ChildComponent was not rerendered
}

I searched in the React Testing Library API and I could not find a way to listen to render events. I thought about Jest mocks, but I don't want to replace ChildComponent, but listen to its render events.

Do you have a proposal to write this test?


Solution

  • I was finally able to make it work. This SO answer gave the info I needed to access the underlying component of a Memoized component.

    So my solution

    import { render, screen } from "@testing-library/react";
    import userEvent from "@testing-library/user-event";
    import { App } from "./App"
    import * as ChildComponentModule from "../../RecordRow/RecordRow";
    
    
    jest.mock("./ChildComponent", () => {
      const ChildComponentModule = jest.requireActual("./ChildComponent");
      return {
        ...ChildComponentModule, // Unnecessary if there's only 1 exported item
        MemoizedChildComponent : {
          ...ChildComponentModule.MemoizedChildComponent,
          type: jest.spyOn(ChildComponentModule.MemoizedChildComponent, "type"), // Spy on the underlying ChildComponent of MemoizedChildComponent
        },
      };
    });
    
    test("ChildComponent was not rerendered", async () => {
      render(<App>);
      jest.clearAllMocks(); // ignore initial renders
      await userEvent.click(...)
    
      // Assert that ChildComponent was not rerendered
    expect(ChildComponentModule.MemoizedChildComponent.type).not.toHaveBeenCalled();
    
    }
    

    In my case, I use named exports. For default exports, you may use this syntax