| id | testing |
|---|---|
| title | Testing with Jest |
| sidebar_position | 4 |
import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem';
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)/)',
],:::note
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.
:::
In order to load mocks provided by RNGH, add the following to your jest config:
"setupFiles": ["./node_modules/react-native-gesture-handler/jestSetup.js"]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.
<CollapsibleCode label="Show composed types definitions" expandedLabel="Hide composed types definitions" lineBounds={[0, 3]} src={` import { createGestureController } from 'react-native-gesture-handler/jest-utils';
createGestureController: (componentOrGesture) => GestureController;
export interface GestureController< TEventPayload extends Record<string, unknown> = Record<string, unknown>,
{ begin: (event?: GestureControllerEvent) => void; activate: (event?: GestureControllerEvent) => void; update: (event?: GestureControllerEvent) => void; end: (event?: GestureControllerEvent) => void; fail: (event?: GestureControllerEvent) => void; cancel: (event?: GestureControllerEvent) => void; }
export type GestureControllerEvent< TEventPayload extends Record<string, unknown> = Record<string, unknown>,
= Partial & { handlerTag?: never; nativeEvent?: never; oldState?: never; state?: never; }; `}/>
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 })
);
});:::note
createGestureController controls lifecycle events directly. It does not generate
pointer input, run platform gesture recognizers, or evaluate relations between
gestures.
:::
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.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.
:::warning
testID must be unique among components rendered in test.
:::
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);
});