Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How should I test functionality accessible only during the lifetime of an async method using @testing-library/react?

I have a React component which allows users to submit potentially long-running queries to a remote service. While the query is running, the component shows a cancel button. I want to test that this button shows up when expected, that its click handler cancels the previous API request, and so on.

Since the button is only present while the async API call is active, the tests I wrote for this purpose make their assertions about the button in the mock implementation of the async API itself. They're not super elegant but I confirmed that they do go red as I expect when I remove parts of the production code.

On upgrading @testing-library/react from 8.0.1 to 9.3.2, although the tests still pass, I now get the following warning several times:

console.error node_modules/@testing-library/react/dist/act-compat.js:52
      Warning: You seem to have overlapping act() calls, this is not supported. Be sure to await previous act() calls before making a new one.

I have reproduced the issue in the following CodeSandbox (be sure to selected the "Tests" tab on the right-hand side and then view the "Console" messages at the bottom right).

Edit sweet-clarke-kq299

The final comment on this GitHub issue says that I shouldn't need to worry about act() as long as I'm using the React Testing Library helpers and functions (which I am). What am I missing?

like image 651
nickform Avatar asked Nov 18 '19 16:11

nickform


1 Answers

Raising an issue (here) against react-testing-library has helped me reach the conclusion that the best approach seems to be not to worry about it too much. The library's author was kind enough to propose a simpler test pattern of just making assertions directly after the action that causes the component to enter the transient state that you're trying to test. For example:

fireEvent.click(getByText("Submit")); // <-- action which triggers an async API call

expect(getByText("Fetching answers")).toBeInTheDocument();

Previously, I had the same expect statement in my mock implementation of the API call triggered by the click. My reasoning was that this was the only way I could be certain that the assertions would run while the component was in the transient state I was trying to test.

AFAICT, these tests are not strictly correct with respect to asynchronous actions because the promise returned by the mock implementation could resolve before the expectation was checked. In practice though, I have observed that the tests rewritten to the simpler approach:

  • do not provoke the warning about overlapping act() calls in the OP
  • can be made to fail as expected if I change the non-test code to break the behaviour under test
  • have not so far shown any intermittent failures
  • are far easier to read and understand

This question has never attracted much attention but I hope recording the answer I eventually arrived at myself might help others out in future.

like image 66
nickform Avatar answered Oct 18 '22 08:10

nickform