• Stars
    star
    1,269
  • Rank 37,079 (Top 0.8 %)
  • Language
    TypeScript
  • License
    MIT License
  • Created over 2 years ago
  • Updated about 1 month ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Performance testing companion for React and React Native

Reassure

Performance testing companion for React and React Native.

Callstack x Entain

Read The Docs


The problem

You want your React Native app to perform well and fast at all times. As a part of this goal, you profile the app, observe render patterns, apply memoization in the right places, etc. But it's all manual and too easy to unintentionally introduce performance regressions that would only get caught during QA or worse, by your users.

This solution

Reassure allows you to automate React Native app performance regression testing on CI or a local machine. The same way you write your integration and unit tests that automatically verify that your app is still working correctly, you can write performance tests that verify that your app still working performantly.

You can think about it as a React performance testing library. In fact, Reassure is designed to reuse as much of your React Native Testing Library tests and setup as possible.

Reassure works by measuring render characteristics – duration and count – of the testing scenario you provide and comparing that to the stable version. It repeates the scenario multiple times to reduce impact of random variations in render times caused by the runtime environment. Then it applies statistical analysis to figure out whether the code changes are statistically significant or not. As a result, it generates a human-readable report summarizing the results and displays it on the CI or as a comment to your pull request.

Installation and setup

In order to install Reassure run following command in your app folder:

Using yarn

yarn add --dev reassure

Using npm

npm install --save-dev reassure

You will also need a working Jest setup as well as one of either React Native Testing Library or React Testing Library.

You can check our example projects:

Reassure will try to detect which Testing Library you have installed. In case both React Native Testing Library and React Testing Library are present it will warn you about that and give a precedence to React Native Testing Library. You can explicitly specify Testing Library to by used by using configure option:

configure({ testingLibrary: 'react-native' })
// or
configure({ testingLibrary: 'react' })

You should set it in your Jest setup file and you can override it in particular test files if needed.

Writing your first test

Now that the library is installed, you can write you first test scenario in a file with .perf-test.js/.perf-test.tsx extension:

// ComponentUnderTest.perf-test.tsx
import { measurePerformance } from 'reassure';
import { ComponentUnderTest } from './ComponentUnderTest';

test('Simple test', async () => {
  await measurePerformance(<ComponentUnderTest />);
});

This test will measure render times of ComponentUnderTest during mounting and resulting sync effects.

Note: Reassure will automatically match test filenames using Jest's --testMatch option with value "<rootDir>/**/*.perf-test.[jt]s?(x)". However, if you would like to pass a custom --testMatch option, you may add it to the reassure measure script in order to pass your own glob. More about --testMatch in Jest docs

Writing async tests

If your component contains any async logic or you want to test some interaction you should pass the scenario option:

import { measurePerformance } from 'reassure';
import { screen, fireEvent } from '@testing-library/react-native';
import { ComponentUnderTest } from './ComponentUnderTest';

test('Test with scenario', async () => {
  const scenario = async () => {
    fireEvent.press(screen.getByText('Go'));
    await screen.findByText('Done');
  };

  await measurePerformance(<ComponentUnderTest />, { scenario });
});

The body of the scenario function is using familiar React Native Testing Library methods.

In case of using a version of React Native Testing Library lower than v10.1.0, where screen helper is not available, the scenario function provides it as its first argument:

import { measurePerformance } from 'reassure';
import { fireEvent } from '@testing-library/react-native';

test('Test with scenario', async () => {
  const scenario = async (screen) => {
    fireEvent.press(screen.getByText('Go'));
    await screen.findByText('Done');
  };

  await measurePerformance(<ComponentUnderTest />, { scenario });
});

If your test contains any async changes, you will need to make sure that the scenario waits for these changes to settle, e.g. using findBy queries, waitFor or waitForElementToBeRemoved functions from RNTL.

For more examples look into our example apps:

Measuring test performance

In order to measure your first test performance you need to run following command in terminal:

yarn reassure

This command will run your tests multiple times using Jest, gathering render statistics, and will write them to .reassure/current.perf file. In order to check your setup, check if the output file exists after running the command for the first time.

Note: You can add .reassure/ folder to your .gitignore file to avoid accidentally committing your results.

Reassure CLI will automatically try to detect your source code branch name and commit hash when you are using Git. You can override these options, e.g. if you are using different version control system:

yarn reassure --branch [branch name] --commit-hash [commit hash]

Write performance testing script

In order to detect performance changes, you need to measure the performance of two versions of your code current (your modified code), and baseline (your reference point, e.g. main branch). In order to measure performance on two different branches you need to either switch branches in git or clone two copies of your repository.

We want to automate this task, so it can run on the CI. In order to do that you will need to create a performance testing script. You should save it in your repository, e.g. as reassure-tests.sh.

A simple version of such script, using branch changing approach is as follows:

#!/usr/bin/env bash
set -e

BASELINE_BRANCH=${BASELINE_BRANCH:="main"}

# Required for `git switch` on CI
git fetch origin

# Gather baseline perf measurements
git switch "$BASELINE_BRANCH"
yarn install --force
yarn reassure --baseline

# Gather current perf measurements & compare results
git switch --detach -
yarn install --force
yarn reassure

CI setup

To make setting up the CI integration and all prerequisites more convenient, we have prepared a CLI command which will generate all necessary templates for you to get started with.

Simply run:

yarn reassure init

This will generate the following file structure

├── <ROOT>
│   ├── reassure-tests.sh
│   ├── dangerfile.ts/js (or dangerfile.reassure.ts/js if dangerfile.ts/js already present)
│   └── .gitignore

Options

You can also use the following options in order to further adjust the script

--verbose (optional)

This is one of the options controlling the level of logs printed into the command prompt while running reassure scripts. It will

--silent (optional)

Just like the previous, this option also controls the level of logs. It will suppress all logs besides explicit errors.

Scaffolding

CI Script (reassure-tests.sh)

Basic script allowing you to run Reassure on CI. More on the importance and structure of this file in the following section.

Dangerfile

If your project already contains a dangerfile.ts/js, the CLI will not override it in any way. Instead, it will generate a dangerfile.reassure.ts/js file which will allow you to compare and update your own at your own convenience.

.gitignore

If .gitignore file is present and no mentions of reassure appear within it, the script will append the .reassure/ directory to its end.

CI script (reassure-tests.sh)

In order to detect performance changes, you need to measure the performance of two versions of your code current (your modified code), and baseline (your reference point, e.g. main branch). In order to measure performance on two different branches you need to either switch branches in git or clone two copies of your repository.

We want to automate this task, so it can run on the CI. In order to do that you will need to create a performance testing script. You should save it in your repository, e.g. as reassure-tests.sh.

A simple version of such script, using branch changing approach is as follows:

#!/usr/bin/env bash
set -e

BASELINE_BRANCH=${BASELINE_BRANCH:="main"}

# Required for `git switch` on CI
git fetch origin

# Gather baseline perf measurements
git switch "$BASELINE_BRANCH"
yarn install --force
yarn reassure --baseline

# Gather current perf measurements & compare results
git switch --detach -
yarn install --force
yarn reassure

Integration

As a final setup step you need to configure your CI to run the performance testing script and output the result. For presenting output at the moment we integrate with Danger JS, which supports all major CI tools.

Updating existing Dangerfile

You will need a working Danger JS setup.

Then add Reassure Danger JS plugin to your dangerfile :

// /<project_root>/dangerfile.reassure.ts (generated by the init script)

import path from 'path';
import { dangerReassure } from 'reassure';

dangerReassure({
  inputFilePath: path.join(__dirname, '.reassure/output.md'),
});

Creating Dangerfile

If you do not have a Dangerfile (dangerfile.js or dangerfile.ts) yet, you can use the one generated by the reassure init script without making any additional changes.

You can also find it in our example file Dangerfile.

Updating the CI configuration file

Finally run both performance testing script & danger in your CI config:

- name: Run performance testing script
  run: ./reassure-tests.sh

- name: Run Danger.js
  run: yarn danger ci
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

You can also check our example GitHub workflow.

The above example is based on GitHub Actions, but it should be similar to other CI config files and should only serve as a reference in such cases.

Note: Your performance test will run much longer than regular integration tests. It's because we run each test scenario multiple times (by default 10), and we repeat that for two branches of your code. Hence, each test will run 20 times by default. That's unless you increase that number even higher.

Assessing CI stability

During performance measurements we measure React component render times with microsecond precision using React.Profiler. This means that the same code will run faster or slower depending on the machine. For this reason, baseline & current measurements need to be run on the same machine. Optimally, they should be run one after another.

Moreover, in order to achieve meaningful results your CI agent needs to have stable performance. It does not matter really if your agent is fast or slow as long as it is consistent in its performance. That's why during the performance tests the agent should not be used for any other work that might impact measuring render times.

In order to help you assess your machine stability, you can use reassure check-stability command. It runs performance measurements twice for the current code, so baseline and current measurements refer to the same code. In such case the expected changes are 0% (no change). The degree of random performance changes will reflect the stability of your machine. This command can be run both on CI and local machines.

Normally, the random changes should be below 5%. Results of 10% and more considered too high and mean that you should work on tweaking your machine stability.

Note: As a trick of last resort you can increase the run option, from the default value of 10 to 20, 50 or even 100, for all or some of your tests, based on the assumption that more test runs will even out measurement fluctuations. That will however make your tests run even longer.

You can refer to our example GitHub workflow.

Analyzing results

Markdown report

Looking at the example you can notice that test scenarios can be assigned to certain categories:

  • Significant Changes To Render Duration shows test scenario where the change is statistically significant and should be looked into as it marks a potential performance loss/improvement
  • Meaningless Changes To Render Duration shows test scenarios where the change is not stastatistically significant
  • Changes To Render Count shows test scenarios where render count did change
  • Added Scenarios shows test scenarios which do not exist in the baseline measurements
  • Removed Scenarios shows test scenarios which do not exist in the current measurements

API

Measurements

measurePerformance function

Custom wrapper for the RNTL render function responsible for rendering the passed screen inside a React.Profiler component, measuring its performance and writing results to the output file. You can use optional options object allows customizing aspects of the testing

async function measurePerformance(ui: React.ReactElement, options?: MeasureOptions): Promise<MeasureRenderResult> {

MeasureOptions type

interface MeasureOptions {
  runs?: number;
  warmupRuns?: number;
  wrapper?: React.ComponentType<{ children: ReactElement }>;
  scenario?: (view?: RenderResult) => Promise<any>;
}
  • runs: number of runs per series for the particular test
  • warmupRuns: number of additional warmup runs that will be done and discarded before the actual runs (default 1).
  • wrapper: React component, such as a Provider, which the ui will be wrapped with. Note: the render duration of the wrapper itself is excluded from the results, only the wrapped component is measured.
  • scenario: a custom async function, which defines user interaction within the ui by utilized RNTL functions

Configuration

Default configuration

The default config which will be used by the measuring script. This configuration object can be overridden with the use of the configure function.

type Config = {
  runs?: number;
  warmupRuns?: number;
  outputFile?: string;
  verbose?: boolean;
  testingLibrary?:
    | 'react-native'
    | 'react'
    | { render: (component: React.ReactElement<any>) => any; cleanup: () => any };
};
const defaultConfig: Config = {
  runs: 10,
  warmupRuns: 1,
  outputFile: '.reassure/current.perf',
  verbose: false,
  testingLibrary: undefined, // Will try auto-detect first RNTL, then RTL
};

runs: number of repeated runs in a series per test (allows for higher accuracy by aggregating more data). Should be handled with care.

  • warmupRuns: number of additional warmup runs that will be done and discarded before the actual runs. outputFile: name of the file the records will be saved to verbose: make Reassure log more, e.g. for debugging purposes testingLibrary: where to look for render and cleanup functions, supported values 'react-native', 'react' or object providing custom render and cleanup functions

configure function

function configure(customConfig: Partial<Config>): void;

You can use the configure function to override the default config parameters.

resetToDefault function

resetToDefault(): void

Reset current config to the original defaultConfig object

Environmental variables

You can use available environmental variables in order to alter your test runner settings.

  • TEST_RUNNER_PATH: an alternative path for your test runner. Defaults to 'node_modules/.bin/jest' or on Windows 'node_modules/jest/bin/jest'
  • TEST_RUNNER_ARGS: a set of arguments fed to the runner. Defaults to '--runInBand --testMatch "<rootDir>/**/*.perf-test.[jt]s?(x)"'

Example:

TEST_RUNNER_PATH=myOwnPath/jest/bin yarn reassure

External References

Contributing

See the contributing guide to learn how to contribute to the repository and the development workflow.

License

MIT

Made with ❤️ at Callstack

Reassure is an Open Source project and will always remain free to use. The project has been developed in close partnership with Entain and was originally their in-house project. Thanks to their willingness to develop the React & React Native ecosystem, we decided to make it Open Source. If you think it's cool, please star it 🌟

Callstack is a group of React and React Native experts. If you need any help with these or just want to say hi, contact us at [email protected]!

Like the project? ⚛️ Join the Callstack team who does amazing stuff for clients and drives React Native Open Source! 🔥

More Repositories

1

react-native-paper

Material Design for React Native (Android & iOS)
TypeScript
12,811
star
2

linaria

Zero-runtime CSS in JS library
TypeScript
11,659
star
3

haul

Haul is a command line tool for developing React Native apps, powered by Webpack
TypeScript
3,643
star
4

react-native-testing-library

🦉 Simple and complete React Native testing utilities that encourage good testing practices.
TypeScript
3,078
star
5

react-native-builder-bob

👷‍♂️ Simple set of CLIs to scaffold and build React Native libraries for different targets
JavaScript
2,771
star
6

react-native-pager-view

React Native wrapper for the Android ViewPager and iOS UIPageViewController.
TypeScript
2,711
star
7

repack

A Webpack-based toolkit to build your React Native application with full support of Webpack ecosystem.
TypeScript
1,471
star
8

react-native-slider

React Native component exposing Slider from iOS and SeekBar from Android
TypeScript
1,189
star
9

react-native-ios-kit

The missing React Native UI Kit for iOS
JavaScript
521
star
10

react-theme-provider

A set of utilities that help you create your own React theming system in few easy steps
JavaScript
466
star
11

react-native-fbads

Facebook Audience SDK integration for React Native
Java
439
star
12

super-app-showcase

Monorepository template for super app development with React Native and Re.Pack
JavaScript
413
star
13

react-native-image-editor

A library providing an API for cropping images from the web and the local file system.
Kotlin
373
star
14

react-native-brownfield

Set of helpers to make your brownfield integration smooth and easy.
Kotlin
241
star
15

react-native-paper-login-template

The easiest way to start with your application.
TypeScript
239
star
16

react-native-opentok

React Native OpenTok
Objective-C
202
star
17

component-docs

📝 Simple documentation for your React components
TypeScript
137
star
18

react-native-open-source-board

OSS board with triaged React Native issues.
125
star
19

async-storage

Cross platform storage for React, built on top of AsyncStorage
JavaScript
109
star
20

react-native-material-palette

Bringing Material Palette API to React Native
JavaScript
105
star
21

ts-regex-builder

Maintainable regular expressions for TypeScript and JavaScript.
TypeScript
83
star
22

react-native-multibundle

JavaScript
57
star
23

eslint-config-callstack

ESLint preset extending Flow, Prettier and Jest
JavaScript
52
star
24

repack-examples

Repository with examples for Re.Pack
Java
50
star
25

ai-meeting-transcription

AI Tool for meeting transcriptions
Jupyter Notebook
36
star
26

react-native-socket-mobile

React Native module for the Socket Mobile SDK.
JavaScript
35
star
27

web3-react-native-dapp-wagmi

TypeScript
30
star
28

fabric-library-with-custom-cpp-example

Add custom cpp state to your fabric library
Java
29
star
29

super-app-example

This repository presents a compact super-app example from Callstack's blog, illustrating the use of a monorepo structure together with Re.Pack and Module Federation.
Java
27
star
30

hapi-graphql-boilerplate

JavaScript
26
star
31

react-native-detox-example

Example integration of Detox and Jest
Objective-C
23
star
32

linaria-styled

Zero-runtime CSS in JS library for building React components
18
star
33

web3-cross-platform-dapp

TypeScript
17
star
34

parcel-plugin-linaria

Parcel plugin for Linaria (Experimental)
JavaScript
17
star
35

benz-ql

GraphQL server for the Mercedes APIs
JavaScript
16
star
36

generator-node-module

A Yeoman module to author Node libraries with Prettier, Jest, Flow and Babel.
JavaScript
16
star
37

react-native-snapshot-tests

An example of snapshot testing with React Native
Objective-C
16
star
38

delightful-ux-training-app

JavaScript
15
star
39

universal-react-app

JavaScript
12
star
40

workshop-navigation

Navigation in React Native workshop.
JavaScript
12
star
41

ai-cli

AI assistant in your terminal.
TypeScript
11
star
42

github-comment-bot

JavaScript
10
star
43

news-mini-app-showcase

JavaScript
9
star
44

ReactNativeNotes

Notes demo application showing the abilities of React Native for desktop connected with UWP development
TypeScript
9
star
45

talk-universal-react

JavaScript
8
star
46

ai-summarization

AI summarization tool
Jupyter Notebook
7
star
47

react-native-releases-script

Script to generate changelog for RN releases
7
star
48

web3-react-native-dapp-viem

TypeScript
6
star
49

callstack-hackathon-team-ferran

callstack-hackaton-team-ferran
JavaScript
5
star
50

callstack-hackathon-team-dratwa-wojtek

JavaScript
3
star
51

reassure-examples

Examples for Reassure setup with different frameworks.
JavaScript
3
star
52

web3-react-native-testing

Best practices for testing React Native web3 dapps
TypeScript
3
star
53

DMAIC-Performance-Workshop

Dedicated app for the DMAIC Performance Workshop
TypeScript
2
star
54

.github

Templates for all Callstack OSS projects
2
star
55

react-native-paper-codemod

JavaScript
2
star
56

web3-react-native-siwe

Sign-In With Ethereum (SIWE) implementation in React Native
TypeScript
2
star
57

AppleVisionBGFX

Usage of BGFX in visionOS
C++
2
star
58

sample-weather-app

JavaScript
1
star
59

callstack.github.io

Organisation Github Page
HTML
1
star
60

react-native-visionos-docs

Documentation website for react-native-visionos
JavaScript
1
star
61

workshop-universal-sunshine

Universal React Native & React app based on Sunshine: https://github.com/udacity/Sunshine-Version-2
1
star