What is the proper way to wait for reactive variables in tests?

1.4k views Asked by At

Given a reactive variable

export const isLoggedInVar: ReactiveVar<boolean> = cache.makeVar<boolean>(
  !!localStorage.getItem("apiToken")
);

A logout hook simplified as:

function useLogout(): Dispatch<SetStateAction<boolean>> {
  const client = useApolloClient();
  const [loggedOut, setLogout] = useState(false);

  useEffect(() => {
    const resetCache = async (): Promise<void> => {
      localStorage.clear(); // remove api token
      isLoggedInVar(false); // update reactive variable
      client.resetStore();
    };

    if (loggedOut) {
      resetCache();
    }
  }, [loggedOut, client]);

  return setLogout;
}

And a component that calls the hook:

const ProfileHeader: React.FC = () => {
  const setLogout = useLogout();

  return isLoggedInVar() ? (
      <p>
        <Link to="/settings/">
          <span className="m-auto">Settings</span>
        </Link>

        <button
          type="button"
          data-testid="logout-button"
          onClick={(): void => setLogout(true)}
        >
          Log out
        </button>
      </p>
  ) : null;
};

When I write a test for the component, I'm forced to use await waitFor(() => {}); in the test.

describe("ProfileHeader", () => {
  it("should allow users to logout", async () => {
    isLoggedInVar(true);

    const { container, getByTestId, queryByTestId } = renderApollo(
      <MemoryRouter>
        <ProfileHeader />
      </MemoryRouter>
    );

    expect(isLoggedInVar()).toBeTruthy();
    expect(getByTestId("logout-button")).toBeTruthy();
    userEvent.click(getByTestId("logout-button"));

    waitForElementToBeRemoved(queryByTestId("logout-button"));
    expect(localStorage.getItem("apiToken")).toBeNull();

    // await new Promise((resolve) => setTimeout(resolve, 0)); // works too
    await waitFor(() => {});
    expect(isLoggedInVar()).toBeFalsy();
  });
});

But the testing-library documentation mentions:

it is considered bad practice to use an empty callback because it will make the tests more fragile.

I really want to do:

- await waitFor(() => {});
- expect(isLoggedInVar()).toBeFalsy();
+ await waitFor(() => expect(isLoggedInVar()).toBeFalsy());

But this gives me countless errors

An update to ProfileHeader inside a test was not wrapped in act(...).

Which I'm not understanding.

What would be the proper way for my test to wait for the reactive variable to change ?

0

There are 0 answers