question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

How to test RefreshControl "pull-to-refresh" behavior?

See original GitHub issue

Environment

  • react-native: v0.65.1
  • jest: v27.1.0
  • @testing-library/react-native: v7.2.0

Ask your Question

I have a component running a GraphQL query (ApolloClient), and rendering a <ScrollView /> with a <RefreshControl /> that triggers the query’s refetch function:

// MyComponent.tsx
import React from 'react';
import { ActivityIndicator, RefreshControl, ScrollView, ScrollViewProps, Text } from 'react-native';
import { useQuery, gql } from '@apollo/client';

export const TEST_QUERY = gql`
  query TestQuery {
    test {
      placeholder
    }
  }
`;

export interface TestQueryResponse {
  test: {
    placeholder: string;
  };
}

const MyComponent: React.FC<ScrollViewProps> = (props) => {
  const { data, loading, refetch } = useQuery<TestQueryResponse>(TEST_QUERY);

  return (
    <ScrollView
      {...props}
      refreshControl={<RefreshControl refreshing={loading} onRefresh={refetch} />}
    >
      {loading ? (
        <ActivityIndicator accessibilityLabel='loading' />
      ) : data?.test ? (
        <Text>{data.test.placeholder}</Text>
      ) : null}
    </ScrollView>
  );
};

export default MyComponent;

Now, I would like to test the “refetch” behavior. I thought I would try to fire a “refetch” event on the ScrollView:

// MyComponent.test.tsx
import 'react-native';
import React from 'react';
import { MockedProvider } from '@apollo/client/testing';
import { fireEvent, render } from '@testing-library/react-native';

import MyComponent, { TEST_QUERY } from './MyComponent';

describe('MyComponent', () => {
  test('refetch the query when pulled', async () => {
    // I have setup a custom render method using <MockedProvider /> as a wrapper,
    // This is a simplified inline version of my case:
    const { findByText, getByTestId, getByA11yLabel } = render(
      <MockedProvider
        addTypename={false}
        mocks={[
          {
            request: {
              query: TEST_QUERY,
              variables: {},
            },
            result: {
              data: {
                test: {
                  placeholder: 'Hello World',
                },
              },
            },
          },
        ]}
      >
        <MyComponent testID='my-component' />
      </MockedProvider>,
    );

    // wait for initial query to be done loading
    await findByText('Hello World');

    // attempt to simulate a "pulling" event
    fireEvent(getByTestId('my-component'), 'refresh');
    // --> No handler function found for event: "refresh"

    // expect the <ActivityIndicator /> to be rendered
    expect(getByA11yLabel('loading')).toBeDefined();
  });
});

But this test case throws an error on the “fireEvent” line:

No handler function found for event: “refresh”

Am I doing something wrong with the API here? Should I mock something specifically (like <RefreshControl /> itself for this scenario? (or is there a better way to assert that behavior?)

I did not find a list of “events” that can be fired, but from what I understand of the fireEvent API, it seems to expect an onRefresh handler to exists on the target element. Which indeed is not the case here, since the onRefresh handler is defined on the <RefreshControl /> component itself.

If the snippets are not enough, I am happy to provide a repository or such?

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Reactions:3
  • Comments:9 (2 by maintainers)

github_iconTop GitHub Comments

5reactions
Umesh079commented, Dec 2, 2021

Please find below steps to call Pull to Refresh.

  1. First get access to the scrollView component through testID
  2. Fetch refresh control present in the scroll view component
  3. call onRefresh from test case

I have added the below Example for reference.

test('refresh control', async () => {
  const props = createTestProps();
  const component = render(
    <Provider store={store}>
      <ScrollViewComponent {...props} />
    </Provider>
  );

  const scrollView = component.getByTestId('refreshControl');
  expect(scrollView).toBeDefined();

  const { refreshControl } = scrollView.props;
  await act(async () => {
    refreshControl.props.onRefresh();
  });
});
1reaction
Umesh079commented, Feb 3, 2022

@Umesh079 this technically works but only to a certain extent (ie. does it set loading=true?, will it trigger any of the activity indicator logic?).

But, there is a broader problem though, and it’s what I think this issue hints at, is a solution like this violates the principle of “avoiding the dreaded test user”, because it explicitly tests implementation details, and not how an end user would use the product.

I started using RNTL last week and I have hit similar “maturity issues”.

For example, how do I simulate the user typing with the on-screen keyboard (or pressing backspace)? Simply using “fireEvent.changeText” does not cause other events to fire (notably keyboardDidShow!), and does not hit any logic like, “what if the keyboard is now covering the TextInput?”.

What if I have a multiline TextInput and I want to be capturing onContentSizeChange? Again, fireEvent.changeText could care less about triggering this event.

What if I want to be able to capture “onSubmitEditing” for my TextInput because “returnKeyType=“done””? I can’t simulate that press without directly calling the event handler.

At the end of the day, we’re in RN land, and we have to be creative with our testing solutions because they aren’t as mature as what React.js has (they have an entire userEvent library for all kinds of events!).

Sorry for the rant!

I understand your point but in my case, it was plain and simple. My whole target was to achieve maximum coverage so to do that I just called the prop to full fill my requirement.

Read more comments on GitHub >

github_iconTop Results From Across the Web

How to use pull to refresh in Swift? - Stack Overflow
Pull to refresh is built in iOS. You could do this in swift like let refreshControl = UIRefreshControl() override func viewDidLoad() { super....
Read more >
Pull to Refresh Control and One Time Data Fetch Task in SwiftUI
The answer is adding pull to refresh control. SwiftUI introduced a new API called refreshable which refreshes the underlying data when users ...
Read more >
Pull to refresh in SwiftUI with refreshable - Sarunw
If you try to pull to refresh, it will immediately bounce back, which is quite different from UIRefreshControl behavior in UITableView.
Read more >
Using UIRefreshControl | CodePath iOS Cliffnotes
It's easy to include the default pull to refresh control into any scroll view, including UIScrollView, UITableView, or UICollectionView. Step 1: Create the ......
Read more >
RefreshControl - Pull to Refresh in React Native Apps - Enappd
We will implement this in a simple React Native app and test on device. Pull-to-refresh is the functionality we use to refresh data...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found