In React Native Testing Library, how do you simulate scrolling a FlatList of movies with userEvent.scrollTo so that more rows render?
answer
- host ScrollView only; FlatList renders one
- y for vertical, x for horizontal
- pass contentSize and layoutMeasurement
- optional momentumY after the drag
- position remembered between calls
basics
~20 sCall await user.scrollTo(list, { y: 300, contentSize, layoutMeasurement }) on the FlatList's host ScrollView. Without contentSize and layoutMeasurement the list has no dimensions in the test, so it cannot compute a new window of rows to render.
solid answer
~40 sReact Native Testing Library's `user.scrollTo(element, options)` accepts only a host `ScrollView`; a `FlatList` qualifies because it renders one, and anything else throws. The options must match the direction: `y` (and optional `momentumY`) for a vertical list, `x` and `momentumX` for `horizontal`, or it throws. It emits `contentSizeChange`, `scrollBeginDrag`, several `scroll` events and `scrollEndDrag`, then `momentumScrollBegin`, more `scroll` events and `momentumScrollEnd` when momentum is given. Because the test environment has no layout, a `FlatList` only knows how big it and its content are if I pass `contentSize` and `layoutMeasurement`; with them, the virtualized list updates its render window and later rows appear. The helper remembers where the last scroll ended.
code
tsx · 32 linesimport { FlatList, Text } from 'react-native';
import { render, screen, userEvent } from '@testing-library/react-native';
const MOVIES = Array.from({ length: 100 }, (_, i) => ({ id: String(i), title: `Movie ${i}` }));
function MovieList() {
return (
<FlatList
testID="movie-list"
data={MOVIES}
keyExtractor={(m) => m.id}
renderItem={({ item }) => <Text>{item.title}</Text>}
initialNumToRender={10}
updateCellsBatchingPeriod={0} // don't wait on the default 50 ms batching timer
/>
);
}
test('scrolling renders later rows', async () => {
await render(<MovieList />);
const user = userEvent.setup();
expect(screen.queryByText('Movie 15')).not.toBeOnTheScreen();
await user.scrollTo(screen.getByTestId('movie-list'), {
y: 300,
contentSize: { width: 240, height: 480 },
layoutMeasurement: { width: 240, height: 480 },
});
expect(screen.getByText('Movie 15')).toBeOnTheScreen();
await screen.unmount(); // stop pending list work before the test ends
});go deeper
Recall that scrollTo takes the list element and a y offset for vertical lists, and must be awaited.
Explain the host ScrollView requirement, direction checking, the drag and momentum sequences, and why virtualized lists need contentSize and layoutMeasurement.
Write list tests that assert rendered outcomes rather than event counts, and account for the missing layout engine when a list does not update.
Decide which list behaviours are worth component-level scroll tests and which need device-level tests with real layout and performance.
## Why scrolling needs its own helper React Native Testing Library renders without a native layout engine: views have no measured size. A `FlatList` is a virtualized list that renders a window of rows around what is visible, and it decides what is visible from **scroll events carrying the offset, the content size and the viewport size**. A plain `fireEvent.scroll` with an offset gives it too little to act on. `userEvent.scrollTo` builds the sequence of events a real drag produces and lets the test supply the sizes. ## What scrollTo accepts - **Only host `ScrollView` elements.** `FlatList` and `SectionList` render a host `ScrollView`, so they qualify; other elements throw an error naming the type received. - **Options that match the direction.** A vertical list (the default, or `horizontal={false}`) takes `y` and optionally `momentumY`; a `horizontal` list takes `x` and optionally `momentumX`. Mixing them throws. - **Sizes for virtualized lists**: `contentSize` and `layoutMeasurement`, each `{ width, height }`, are passed into the scroll events so the list can recompute its window. Finding the list is one of the few places where a test ID is reasonable: a `FlatList` has no role or label a user perceives, so `testID="movie-list"` on the list is passed to its `ScrollView`. ## The event sequence 1. **Drag**: `contentSizeChange`, `scrollBeginDrag`, several intermediate `scroll` events, `scrollEndDrag` at the target offset. 2. **Momentum** (only with `momentumY` or `momentumX`): `momentumScrollBegin`, more `scroll` events, `momentumScrollEnd`. The number and values of intermediate `scroll` events are not a contract and may change between versions, so assertions should target the final state, not the count of events. The helper **remembers the last offset**, starting at `{ x: 0, y: 0 }`, so a second call continues from where the first ended. ## Scrolling a movie list In the movie app, the catalogue is a vertical `FlatList` that renders about ten rows initially. A test that wants row 15: 1. renders the screen and finds the list by its test ID; 2. calls `await user.scrollTo(list, { y: 300, contentSize: { width: 240, height: 480 }, layoutMeasurement: { width: 240, height: 480 } })`; 3. asserts that `Movie 15` is now on screen. The list in the example sets `updateCellsBatchingPeriod={0}`: a `FlatList` normally schedules low-priority cell updates on a 50 ms batching timer, and a zero period lets that update run during the scroll sequence instead of after the assertion. RNTL's own FlatList test uses the same setup and unmounts at the end so no pending list work outlives the test. Without the two size options the call still emits events, but the list has no dimensions to work from and renders no new window. ## Common mistakes - scrolling the `FlatList`'s parent `View` instead of the list itself; - passing `x` to a vertical list, or `y` to a horizontal row of genre chips; - asserting on the exact number of `scroll` events; - forgetting `await`, so the assertion runs before the list re-renders. ## Scope Tuning the list itself, such as window size, batching or end-reached thresholds, belongs to the list's own configuration. The test's job is to deliver realistic scroll events with sizes and then assert on what the user would see.
- How do you scroll a horizontal row of genre chips with scrollTo?Give the `ScrollView` or `FlatList` `horizontal` and pass `x` (and optionally `momentumX`) instead of `y`. `scrollTo` checks the direction and throws if vertical options are passed to a horizontal list, or the reverse.
- Should a test assert on how many scroll events scrollTo emitted?No. The helper generates several intermediate `scroll` events to mimic a drag, and the docs say their number and values may change in future versions. Assert on the result the user sees, such as which rows render or whether a header collapsed.
saying these in an interview costs you the question
- scrollTo works on any View that has an onScroll prop.
- A FlatList renders new rows after scrollTo even without size options.
- Vertical lists accept x and y options interchangeably.
- scrollTo always starts from offset zero on every call.
- Tests should assert the exact number of scroll events.