Back
Nov 29, 2022

React Performance Testing with Jest

One of the key requirements for modern UI is being performant. No matter how beautiful your app looks and what killer features it offers, it will frustrate your users if it clangs.

React performance optimization is a pretty well-covered topic. If you'd like more information on this,, Fix the slow render before you fix the re-render might be a good starting point. But is there a guarantee these optimizations will last? If not careful, it takes one unstable prop to force the entire app re-render and introduce lags noticeable to the user. Of course, this can be handled by manual testing, but it makes sense to look for some kind of automated solution.

Our experience with Jest

We had this problem at our latest project. This affected a pretty complex application that should be highly responsive to user input. Sometimes an unstable prop would sneak in and force re-render of a large DOM chunk. It was always easy to make it fast again, but we wanted to avoid this in the future. So, we looked for a way to automatically check for changes that affected rendering.

The first step was to understand exactly what metrics we want to catch. Of course, it was tempting to measure something that would matter to the end user. Something like render time would do, but this metric is very fuzzy – so we decided to go with something like the number of renders. This is not ideal, but these tests would be idempotent and independent of their environment. This is also somewhat contrary to the “fix slow renders, not re-renders” philosophy, but it still has a good thing – it helps us catch mistakes without manually re-testing a bunch of cases.

Now for the implementation. We don’t need real DOM for counting re-renders, so we decided to go with Jest. In order to interact with React internals and count re-renders, we used Profiler component. It is basically just a component that accepts children and onRender callback.

image-20221124-154118 (1).png

Technically, this is already enough to count renders. But we wanted to make profiler really easy to use, so we created a Higher-Order Component:

const withProfiler = (Component, id: string = 'rootComponent') => {
  const componentId = `withProfiler${id}`;

  const onRender = () => {
    SnapshotProfiler.__numRenders++;
  };

  const SnapshotProfiler = React.memo((props) => {
    return (
      
        
      
    );
  });

  SnapshotProfiler.__numRenders = 0;

  SnapshotProfiler.clearCounters = function () {
    this.__numRenders = 0;
  };

  return SnapshotProfiler;
};

const Foo = () => (
Foo
) const DecoratedFoo = withProfiler(Foo);

Now we can just wrap any component with this HOC, and we can access the number of times that component has been rendered. Rendering DecoratedFoo will effectively render Foo, but will also store a __numRenders counter. And since it is stored inside the component object, rendering any instance of this component will increase the counter. So, if we render the following jsx:

we will have DecoratedFoo.__numRenders === 2 .

This is all well and good, but it's still a little awkward to have withProfiler HOC to components used in a real app. Fortunately, Jest has flexible tools for mocking, so we could make great use of them.

Let’s say we have a ChessBoard component that renders 64 instances of Square component. On each move it should typically re-render 2 more cells, since a single chess piece moves from square to square. So we can create the following mock setup and write the following tests:

jest.mock("./Square", () => ({
 Square: jest
   .requireActual("../profiler")
   .withProfiler(jest.requireActual("./Square").Square),
}));
describe("Chess performance test", () => {
 function clearCounters() {
     Square.clearCounters();
 }
 beforeEach(() => {
   clearCounters();
 });
 const initialSquareRendersCount = 64;
 test("initial render", () => {
   render();
   expect(Square.__numRenders).toBe(initialCellRendersCount);
 });
 test("piece moving", () => {
   const { rerender } = render();
   clearCounters();
   //
   // here we do some DOM manipulations or push new props to initiate re-render...
   //
   expect(Square.__numRenders).toBe(2);
 });
})

This way we can be sure the chessboard elements are rendered as many times as we need. So if someone accidentally puts a global state in every cell and the entire board will unnecessarily re-render - we will find out.

What we’ve done here is mocked Square, so in tests we have the same component wrapped into withProfiler HOC. Thus, the tests themselves do not alter the component structure, and are rendered just like in a regular environment. The code of the component itself does not change too. So, if we have a complex hierarchy of components and some really specific actions causing too many re-renders of a particular component, we can test just that.

I hope this helps you write more performant web applications. Good luck!

Subscribe for the news and updates

More thoughts
Dec 13, 2022Technology
How to create a timelapse video from frames

We’ll tell you how to create a video timelapse from a sequence of snapshots and provide customers with video playlists optimized for browser playback.

Feb 12, 2020Technology
5 Best Payment Gateways For 2020

We reviewed the best payment gateways in 2020. Here’s our comparison of their features, advantages, and disadvantages.

May 9, 2018Technology
How to Generate PDF Files in Python with Xhtml2pdf, WeasyPrint or Unoconv

Programmatic generation of PDF files is a frequent task when developing applications that can export reports, bills, or questionnaires. In this article, we will consider three common tools for creating PDFs, including their installation and converting principles.

Feb 28, 2017Technology
How to write an API in Django

There is such a term as Remote Procedure Call (RPC). In other words, by using this technology, programs can call functions on remote computers. There are many ways to implement RPC.

Nov 21, 2016Technology
Crawling FTP server with Scrapy

Welcome all who are reading this article. I was given a task of creating a parser (spider) with the Scrapy library and parsing FTP server with data. The parser had to find lists of files on the server and handle each file separately depending on the requirement to the parser.

Feb 18, 2010Technology
User profiles with inheritance in Django

Usually users' profiles are stored in single model. When there are multiple user types, separation is made by some field like user_type.Situation is a little more complicated when different data is needed for each user type.In this article I'll describe how I solve this task.