react-apollo-hooks icon indicating copy to clipboard operation
react-apollo-hooks copied to clipboard

Test runner throws act() warning on waitForNextTick - discussing better solution for React 16.8.1

Open mateusznowosielski opened this issue 6 years ago • 4 comments

After upgrading to React 16.8.1 ([email protected]), tests throw warnings whenever we use waitForNextTick with Promise and setTimeout, which is everywhere when we need to wait for a component to rerender with result data.

act(() => {
    /* fire events that update state */
});

Sample test: https://github.com/trojanowski/react-apollo-hooks-sample-test/blob/master/src/tests/Hello-test.js

As this is just a temporary solution to the problem with useEffect hook

render(null);
await waitForNextTick();

I came up with below solution which is far from pretty and elegant, but in simple scenario it works. Since act callback can't return anything, as for now it's not supported, I had to wrap the above code inside another self executed async function.

// Mocking queries and rendering component goes here
// ...
// Asserting for the rendered mocked data
act( () => {
    (async () => {
        render(null);
        await waitForNextTick();
        expect(container).toMatchSnapshot();
    })()
});

I was wondering if anybody found a better solution to this problem?

mateusznowosielski avatar Feb 15 '19 22:02 mateusznowosielski

@mateusznowosielski it's a known React problem. You can read more about it here: https://github.com/facebook/react/issues/14769. There is a WIP PR with a solution: https://github.com/facebook/react/pull/14853.

trojanowski avatar Feb 20 '19 20:02 trojanowski

I just tried @mateusznowosielski's solution and the expect call within the async function isn't even being called for me. Is there anything super obvious that I'm doing wrong?

it("renders the component", async () => {
  const mockQuery = [
    {
      request: {
        query: gql`
          query HelloQuery {
            hello
          }
        `,
        variables: {},
      },
      result: { data: { hello: "World" } },
    },
  ];
  const waitForNextTick = () => new Promise(resolve => setTimeout(resolve));
  const client = createMockApolloClient(mockQuery);
  const { container, queryByText } = render(
    <ApolloProvider client={client}>
      <MemoryRouter>
        <MyComponent />
      </MemoryRouter>
    </ApolloProvider>
  );
  const loadingStateText = queryByText("Loading!");
  expect(loadingStateText).not.toBeNull();
  act(() => {
    (async () => {
      render(null);
      await waitForNextTick();
      console.log("foo");
      expect(container.firstChild).toMatchSnapshot();
    })();
  });
});

Anything in the async block within act() isn't executed at all. The console doesn't log and the snapshot isn't generated.

EDIT: Nevermind. I gave up on act() until the async act is released. I'm just running jest --silent for the time being. Sucks, but w/e.

Vpr99 avatar Feb 20 '19 21:02 Vpr99

I suppose no one came up with some viable workaround for this yet? The --silent sort of works, but it's annoying if I actually need to see console output.

I wonder if we could somehow utilize the actHack being present the code of the library. I am thinking something like check for process.env.NODE_ENV === 'test' and use the real act in that case. That should pretty much solve the problem imo.

Update: Ok, I tried exactly that just by hardcoding following inside the node_modules/react-apollo-hooks/lib/internal/actHack.js and it works just fine. I will make a PR tomorrow.

"use strict";

exports.__esModule = true;
exports.default = actHack;

var _testUtils = require("react-dom/test-utils");

function actHack(callback) {
  _testUtils.act(() => {
	  callback();
  })
}

danielkcz avatar Mar 06 '19 21:03 danielkcz

Now in react-dom v16.9.0-alpha.0 act works with async functions:

await act(async () => {
    await waitForNextTick();
    ...
})

flytaly avatar Apr 04 '19 16:04 flytaly