ptomasroos / react-native-scrollable-tab-view

Tabbed navigation that you can swipe between, each tab can have its own ScrollView and maintain its own scroll position between swipes. Pleasantly animated. Customizable tab bar
https://www.npmjs.com/package/react-native-scrollable-tab-view
6.93k stars 2.28k forks source link

[Solution] Using with react-native-testing-library: "Unable to find node on an unmounted component." #1116

Open lukewlms opened 4 years ago

lukewlms commented 4 years ago

I keep running into this issue when attempting to test this component, and I'm just sharing my solution here as I don't have time to create a minimal repro repository.

Feel free to close as desired, just wanted this to be here for others' reference to quickly resolve this issue.

This repros on latest (1.0.0).

I'm attempting to test Apollo Client which requires adding a 0-duration timeout promise to tick over and actually run the query, before testing the "loaded" version of the component.

The error is:

    Unable to find node on an unmounted component.

      102 |   );
      103 |
    > 104 |   const isLoading = () => rr.queryByText(/Loading/) !== null;
          |                              ^
      105 |   const isShowingData = () => rr.queryByText(/Noooootes/) !== null;
      106 |
      107 |   expect(isLoading()).toBe(true);

      at queryByText (src/components/workouts/SelectWorkoutScreen.test.tsx:104:30)
      at Object.isLoading (src/components/workouts/SelectWorkoutScreen.test.tsx:112:10)
      at tryCatch (node_modules/@babel/runtime/node_modules/regenerator-runtime/runtime.js:45:40)
      at Generator.invoke [as _invoke] (node_modules/@babel/runtime/node_modules/regenerator-runtime/runtime.js:274:22)
      at Generator.prototype.<computed> [as next] (node_modules/@babel/runtime/node_modules/regenerator-runtime/runtime.js:97:21)
      at tryCatch (node_modules/@babel/runtime/node_modules/regenerator-runtime/runtime.js:45:40)
      at invoke (node_modules/@babel/runtime/node_modules/regenerator-runtime/runtime.js:135:20)
      at node_modules/@babel/runtime/node_modules/regenerator-runtime/runtime.js:145:13
      at tryCallOne (node_modules/react-native/node_modules/promise/lib/core.js:37:12)
      at node_modules/react-native/node_modules/promise/lib/core.js:123:15

This doesn't happen before the timeout promise, but does after.

Solution

For now I'm resolving with a simple mock:

jest.mock("react-native-scrollable-tab-view", () => {
  return {
    __esModule: true, // needed to mock the default export
    default: (props: { children: ReactNode[] }) => {
      // Just render first child
      return props.children[0];
    },
    DefaultTabBar: () => null,
  };
});

And here's the test code as FYI:

test("Select Workout screen renders with query", async () => {
  const rr = render(
    <MockedProvider mocks={[mock, createOnboardingEventsMock()]}>
      <SelectWorkoutScreen />
    </MockedProvider>,
  );

  const isLoading = () => rr.queryByText(/Loading/) !== null;
  const isShowingData = () => rr.queryByText(/Notes/) !== null;

  expect(isLoading()).toBe(true);
  expect(isShowingData()).not.toBe(true);

  await wait();

  expect(isLoading()).not.toBe(true);
  expect(isShowingData()).toBe(true);
});

// Wait a tick so the query runs
// https://www.apollographql.com/docs/react/development-testing/testing/#testing-final-state
export async function wait(ms = 0) {
  await act(async () => {
    return new Promise(resolve => {
      setTimeout(resolve, ms);
    });
  });
}