react-performance-testing
    TypeScript icon, indicating that this package has built-in type declarations

    2.0.0 • Public • Published

    react-performance-testing

    npm codecov GitHub Workflow Status GitHub Workflow Status License: MIT

    This library is perfect for testing React or ReactNative runtime performance. react-performance-testing counts the number of renders and the render time in a test environment.

    Table of Contents

    The problem

    If you need to develop high-performance features, you need to count renders and render time. Normally you would have to go through the arduous process of manually checking dev-tools or Lighthouse. With react-performance-testing you can automate this process, saving you time and ensuring you always have one eye on performance.

    The solution

    react-performance-testing monkey patches React to provide you with an API that can count the number of renders and measure render time.

    Installation

    npm:

    npm install --save-dev react-performance-testing

    yarn:

    yarn add --dev react-performance-testing

    Use jest-performance-testing for a great testing experience.

    Additionally, you can use performance-testing-cli if you use renderTime. If you use this library, you can execute test for each files. Therefore you will not need to test one by one.

    Example

    count renders

    test('should have two renders when state is updated', async () => {
      const Counter = () => {
        const [count, setCount] = React.useState(0);
        return (
          <div>
            <p>{count}</p>
            <button type="button" onClick={() => setCount((c) => c + 1)}>
              count
            </button>
          </div>
        );
      };
      const Component = () => {
        return <Counter />;
      };
    
      const { renderCount } = perf(React);
    
      render(<Component />);
    
      fireEvent.click(screen.getByRole('button', { name: /count/i }));
    
      await wait(() => expect(renderCount.current.Counter.value).toBe(2));
    });
    
    test('should have two renders when state is updated with multiple of the same component', async () => {
      const Counter = ({ testid }) => {
        const [count, setCount] = React.useState(0);
        return (
          <div>
            <p>{count}</p>
            <button
              data-testid={testid}
              type="button"
              onClick={() => setCount((c) => c + 1)}
            >
              count
            </button>
          </div>
        );
      };
      const Component = () => {
        return (
          <div>
            <Counter />
            <Counter testid="button" />
            <Counter />
          </div>
        );
      };
    
      const { renderCount } = perf(React);
    
      render(<Component />);
    
      fireEvent.click(screen.getByTestId('button'));
    
      await wait(() => {
        expect(renderCount.current.Counter[0].value).toBe(1);
        expect(renderCount.current.Counter[1].value).toBe(2);
        expect(renderCount.current.Counter[2].value).toBe(1);
      });
    });

    measure render time

    If you want to use renderTime, please check out the docs: renderTime.

    test('render time should be less than 16ms', async () => {
      const Counter = () => {
        const [count, setCount] = React.useState(0);
        return (
          <div>
            <p>{count}</p>
            <button type="button" onClick={() => setCount((c) => c + 1)}>
              count
            </button>
          </div>
        );
      };
    
      const { renderTime } = perf(React);
    
      render(<Counter />);
    
      fireEvent.click(screen.getByRole('button', { name: /count/i }));
    
      await wait(() => {
        // 16ms is meaning it is 60fps
        expect(renderTime.current.Counter.mount).toBeLessThan(16);
        // renderTime.current.Counter.updates[0] is second render
        expect(renderTime.current.Counter.updates[0]).toBeLessThan(16);
      });
    });
    
    test('should measure re-render time when state is updated with multiple of the same component', async () => {
      const Counter = ({ testid }) => {
        const [count, setCount] = React.useState(0);
        return (
          <div>
            <p>{count}</p>
            <button
              data-testid={testid}
              type="button"
              onClick={() => setCount((c) => c + 1)}
            >
              count
            </button>
          </div>
        );
      };
      const Component = () => {
        return (
          <div>
            <Counter />
            <Counter testid="button" />
            <Counter />
          </div>
        );
      };
    
      const { renderTime } = perf(React);
    
      render(<Component />);
    
      fireEvent.click(screen.getByTestId('button'));
    
      await wait(() => {
        expect(renderTime.current.Counter[0].updates).toHaveLength(0);
        expect(renderTime.current.Counter[1].updates[0]).toBeLessThan(16);
        expect(renderTime.current.Counter[2].updates).toHaveLength(0);
      });
    });

    API

    If you use the API with a large component, the component's performance could be affected because we monkey patch React. Therefore, if you want to measure accurately, you should use the API with components that have one feature like List, Modal etc.

    perf

    perf method observes your component. So you can get the renderCount to count the number of renders and renderTime to measure render time.

    const { renderCount, renderTime } = perf(React);

    Note that You need to invoke the perf method before the render method is invoked. Additionally, You need to pass React to the perf method because we are monkey patching React.

    Note: You need to wrap the returned value with wait method.

    renderCount

    renderCount will count the number of renders.

    const Component = () => <p>test</p>;
    const { renderCount } = perf(React);
    // render is imported from react-testing-library
    render(<Component />);
    wait(() => console.log(renderCount.current.Component.value)); // output: 1

    Note: You need to set a display name. If you have an anonymous component, we can not set the renderCount property correctly.

    Properties
    • renderCount.current
      • ComponentName: string | Array
        • value: number

    Note: If you have the same component, these components combine into an array

    renderTime

    renderTime will count the time elapsed between renders.

    const Component = () => <p>test</p>;
    const { renderTime } = perf(React);
    // render is imported from react-testing-library
    render(<Component />);
    wait(() => {
      console.log(renderTime.current.Component.mount); // output: ...ms
      console.log(renderTime.current.Component.updates); // output: []
    });

    Note: If you want to measure render time, you need to test renders one by one. V8 has a feature called inline caching, so if you measure just the result there will be a large difference. Therefore You need to execute tests one by one like jest --testNamePattern=... or jest src/something.test.js.

    Note: You need to set a display name. If you have an anonymous component, we can not set a property to renderTime correctly.

    Properties
    • renderCount.current
      • ComponentName: string | Array
        • mount: number ... This property has the initial render time.
        • updates: Array<number> ... This property has the second and the subsequent render time (the second render is the index of 0)

    Note: If you have the duplicate components, these components combine into array
    Note: time is measured in ms, milliseconds elapsed.

    wait

    The wait method is a feature that waits for renderCount or renderTime to be assigned. We need to wrap all returned values from perf() because we are assigning renderCount and renderTime asynchronous. If we were to assign some value to renderCount or renderTime synchronous, extra processing would be included in the rendering phase.

    wait(() => console.log(renderTime.current.Component));

    cleanup

    The cleanup method is executed automatically in afterEach() if you are using Jest, Mocha, and Jasmine. You need to clean up your component by using cleanup. If your testing library has afterEach(), you need to invoke cleanup() manually.

    ReactNative

    If you are using ReactNative, you need to import modules from react-performance-testing/native.

    import { perf } from 'react-performance-testing/native';

    TypeScript

    If you are using Typescript, you can get benefits from type inference as seen below.

    const Text = (): React.ReactElement => <p>test</p>;
    const Component = (): React.ReactElement => (
      <div>
        <Text />
        <Text />
      </div>
    );
    
    // If you didn't pass your type to the type argument
    const { renderCount, renderTime } = perf(React);
    renderCount.current // Editor will suggest `Text | Text[]` and `Component | Component[]`
    
    // If you passed your type to the type argument
    const { renderCount, renderTime } = perf <{ Text: unknown[], Component: unknown }> React;
    renderCount.current // Editor will suggest `Text[]` and `Component`

    You can pass the {ComponentName: unknown or unknown[]} type for the type argument. If you passed to the type argument, then the editor will suggest the correct type dependent on the passed type.

    Note: If you are using ts-jest, you need to combine it with babel. You can check the way to set up config here. This is because, TypeScript compiler can not compile named arrow functions correctly. You can read up on the issue here: https://github.com/microsoft/TypeScript/issues/6433.

    Tips

    Performance

    This library is using the Proxy API to optimize testing speed. So you should use either renderCount or renderTime in a single test case. If you use both variables or you are testing a large component, the testing time will be a little slower.

    Anonymous Component

    If you are using an anonymous component, this library doesn't work correctly. To make this library work correctly, you need to set the display name as seen below.

    React.memo(function MemoComponent() {
      return <p>test</p>;
    });
    
    // or
    
    const MemoComponent = () => <p>test</p>;
    React.memo(MemoComponent);

    Setting a display name will get benefits not only from this library but also when you debug in React.

    Hooks

    If you are using @testing-library/react-hooks, you can check the number of renders with the perf method as bellows.

    const { renderCount } = perf(React);
    const { result } = renderHook(() => {
      /**
       * use some hooks
       */
    });
    
    // You can get value from the TestHook component
    wait(() => console.log(renderCount.current.TestHook.value));

    This is because the renderHook method is wrapping callbacks with the TestHook component.

    LICENSE

    MIT

    Install

    npm i react-performance-testing

    DownloadsWeekly Downloads

    1,662

    Version

    2.0.0

    License

    MIT

    Unpacked Size

    93.6 kB

    Total Files

    90

    Last publish

    Collaborators

    • keiya01