Testing with Jest
Setup
Jest configuration
In order to use functions provided by Gesture Handler, add react-native-gesture-handler to transformIgnorePatterns in jest config.
transformIgnorePatterns: [
'node_modules/(?!((jest-)?react-native|react-native-gesture-handler)/)',
],
Be careful when adding multiple entries to transformIgnorePatterns. Since Jest ignores a file if it matches any pattern in the list, splitting negative lookaheads into separate strings often causes them to override each other. It is safer to combine your exceptions into a single regex using the | operator. See the Jest documentation for an example.
Mocking native modules
In order to load mocks provided by RNGH, add the following to your jest config:
"setupFiles": ["./node_modules/react-native-gesture-handler/jestSetup.js"]
Example jest config
- jest.config.js
- package.json
module.exports = {
preset: '@react-native/jest-preset',
transformIgnorePatterns: [
'node_modules/(?!((jest-)?react-native|react-native-gesture-handler)/)',
],
setupFiles: ['react-native-gesture-handler/jestSetup.js'],
};
"jest": {
"preset": "@react-native/jest-preset",
"transformIgnorePatterns": [
"node_modules/(?!((jest-)?react-native|react-native-gesture-handler)/)"
],
"setupFiles": [
"react-native-gesture-handler/jestSetup.js"
]
}
Testing Gestures' and Gesture handlers' callbacks
RNGH provides the following APIs for triggering selected handlers:
createGestureControllerto imperatively control a gesture lifecycle one step at a time.fireGestureHandlerto dispatch a complete event stream.getByGestureTestIdto find a gesture by its test ID.
createGestureController
import { createGestureController } from 'react-native-gesture-handler/jest-utils';
createGestureController: (componentOrGesture) => GestureController;
Creates an imperative controller that dispatches gesture lifecycle events one step
at a time. This allows the test to assert application state between lifecycle steps
without manually supplying state, oldState, or handlerTag.
componentOrGesture can be:
- A Gesture Handler component found using a Jest query such as
getByTestId. - A gesture object.
- A gesture test ID string.
When a gesture object is passed directly, the event payload type is inferred from the gesture. Every lifecycle method accepts an optional partial event payload and fills omitted handler-specific properties with defaults.
The controller exposes the following methods:
| Method | Behavior |
|---|---|
begin() | Starts a stream and calls onBegin. If the previous stream finished, the controller resets it before beginning. |
activate() | Activates a begun stream and calls onActivate. |
update() | Dispatches an update for an active stream and calls onUpdate. It can be called multiple times. |
end() | Ends a begun or active stream. Calls onDeactivate if active, then onFinalize with canceled: false. |
fail() | Fails a begun or active stream. Calls onDeactivate if active, then onFinalize with canceled: true. |
cancel() | Cancels a begun or active stream. Calls onDeactivate if active, then onFinalize with canceled: true. |
Calling begin() again after end(), fail(), or cancel() starts another stream with the same controller.
Calling methods in an invalid order throws an error. State-machine fields such as
state, oldState, handlerTag, and nativeEvent cannot be supplied in event
payloads because the controller manages them internally.
test('updates application state after each gesture step', () => {
const onBegin = jest.fn();
const onActivate = jest.fn();
const onUpdate = jest.fn();
const onDeactivate = jest.fn();
const onFinalize = jest.fn();
const panGesture = renderHook(() =>
usePanGesture({
disableReanimated: true,
onBegin,
onActivate,
onUpdate,
onDeactivate,
onFinalize,
})
).result.current;
const controller = createGestureController(panGesture);
controller.begin();
expect(onBegin).toHaveBeenCalledTimes(1);
controller.activate();
expect(onActivate).toHaveBeenCalledTimes(1);
controller.update({ translationX: 50 });
expect(onUpdate).toHaveBeenCalledWith(
expect.objectContaining({ translationX: 50 })
);
controller.end();
expect(onDeactivate).toHaveBeenCalledTimes(1);
expect(onFinalize).toHaveBeenCalledWith(
expect.objectContaining({ canceled: false })
);
});
createGestureController controls lifecycle events directly. It does not generate
pointer input, run platform gesture recognizers, or evaluate relations between
gestures.
fireGestureHandler
import { fireGestureHandler } from 'react-native-gesture-handler/jest-utils';
fireGestureHandler: (componentOrGesture, eventList) => void;
Simulates one event stream (i.e. event sequence starting with BEGIN state and ending
with one of END/FAIL/CANCEL states), calling appropriate callbacks associated with given gesture handler.
-
componentOrGesture- Either Gesture Handler component found byJestqueries (e.g.getByTestId) or Gesture found bygetByGestureTestId() -
eventList- Event data passed to appropriate callback. RNGH fills event list if required data is missing using these rules:oldStateis filled using state of the previous event.BEGINevents useUNDETERMINEDvalue as previous event.- Events after first
ACTIVEstate can omitstatefield. - Handler specific data is filled (e.g.
numberOfTouches,xfields) with defaults. - Missing
BEGINandENDevents are added with data copied from first and last passed event, respectively. - If first event doesn't have
statefield, theACTIVEstate is assumed.
Some eventList examples:
const oldStateFilled = [
{ state: State.BEGAN },
{ state: State.ACTIVE },
{ state: State.END },
]; // three events with specified state are fired.
const implicitActiveState = [
{ state: State.BEGAN },
{ state: State.ACTIVE },
{ x: 5 },
{ state: State.END },
]; // 4 events, including two ACTIVE events (second one has overridden additional data).
const implicitBegin = [
{ x: 1, y: 11 },
{ x: 2, y: 12, state: State.FAILED },
]; // 3 events, including implicit BEGAN, one ACTIVE, and one FAILED event with additional data.
const implicitBeginAndEnd = [
{ x: 5, y: 15 },
{ x: 6, y: 16 },
{ x: 7, y: 17 },
]; // 5 events, including 3 ACTIVE events and implicit BEGAN and END events. BEGAN uses first event's additional data, END uses last event's additional data.
const allImplicits = []; // 3 events, one BEGIN, one ACTIVE, one END with defaults.
getByGestureTestId
import { getByGestureTestId } from 'react-native-gesture-handler/jest-utils';
getByGestureTestId: (testID: string) => Gesture;
Returns opaque data type associated with gesture. Gesture is found via testID attribute in rendered
components.
testID must be unique among components rendered in test.
fireGestureHandler example
Extracted from RNGH tests, check api_v3.test.tsx for full implementation.
test('Pan gesture', () => {
const onBegin = jest.fn();
const onStart = jest.fn();
const panGesture = renderHook(() =>
usePanGesture({
disableReanimated: true,
onBegin: (e) => onBegin(e),
onActivate: (e) => onStart(e),
})
).result.current;
fireGestureHandler(panGesture, [
{ oldState: State.UNDETERMINED, state: State.BEGAN },
{ oldState: State.BEGAN, state: State.ACTIVE },
{ oldState: State.ACTIVE, state: State.ACTIVE },
{ oldState: State.ACTIVE, state: State.END },
]);
expect(onBegin).toHaveBeenCalledTimes(1);
expect(onStart).toHaveBeenCalledTimes(1);
});