2

I am rendering the component in an integration test of my single page app and attempting to follow a happy path from landing on a page to the first question of a quiz / memory game.

Warning: An update to GamePlay inside a test was not wrapped in act(...).

The second line is the one that triggers multiple warnings...

    const firstItem = getAllByRole("listitem")[0]
    userEvent.click(firstItem)

It clicks the first option in a list which causes the gameplay component to render. That component shows a word for 5000ms then hides it using a setTimeout which is set in the component's useEffect hook (below). An answer input field is also disabled and enabled in this routine.

    function hideQuestion() {
        setInputDisabled(false)
        setDisplayWord(false)
    }

    const [displayWord, setDisplayWord]= useState(true);

    useEffect(()=>{
        setTimeout(hideQuestion, 5000)
    },[displayWord])

    const [inputDisabled, setInputDisabled] = useState(true);

From reading around I think it might be this use of setTimeout that's responsible but none of the mitigations I have tried have done anything. I tried wrapping the click line in an "act" function and that did nothing. I tried making the test async and awaiting that line but again that did nothing - I still get this error (and several similar)...

  console.error
    Warning: An update to GamePlay inside a test was not wrapped in act(...).
    
    When testing, code that causes React state updates should be wrapped into act(...):
    
    act(() => {
      /* fire events that update state */
    });
    /* assert on the output */
    
    This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act
        at GamePlay (/home/user/code/spellingapp/src/components/GamePlay.js:3:20)
        at MainPage (/home/user/code/spellingapp/src/components/MainPage.js:8:29)
        at Route (/home/user/code/spellingapp/node_modules/react-router/cjs/react-router.js:470:29)
        at Switch (/home/user/code/spellingapp/node_modules/react-router/cjs/react-router.js:676:29)
        at Router (/home/user/code/spellingapp/node_modules/react-router/cjs/react-router.js:99:30)
        at BrowserRouter (/home/user/code/spellingapp/node_modules/react-router-dom/cjs/react-router-dom.js:67:35)
        at App (/home/user/code/spellingapp/src/App.js:16:29)

       5 |   function hideQuestion() {
       6 |     // console.log("I have hidden the word and enabled the text box")
    >  7 |     setInputDisabled(false)

I tried reading this article: https://kentcdodds.com/blog/fix-the-not-wrapped-in-act-warning

But I'm struggling to understand it fully and relate it to my own code. I think the gist of it is that you will get these warning when there are "unexpected" state changes but I can't see how I'm supposed to make mine expected. My app is a real time game so the whole thing runs off timers - one timer finishes and another one starts. I don't want to have to run through a full 10 minutes of gameplay so it can finish neatly. I just want to test from landing page to the first interaction as a smoke test of the core integration logic.

Right now all I can think of is to make a test fixture with only one question so I can run the whole game end to end, but this seems like overkill when my current test runs fine (apart from the warnings) and does all I want it to do right now. I'd appreciate any pointers or suggestions, even if it's that I'm just barking up the wrong tree. Thanks!

2
  • What do you have after the userEvent.click line? Care to share the full test that triggers the warning? Commented Mar 23, 2021 at 18:50
  • @juliomalves - There's nothing else. Well there is, but I commented it all out and the error still persists. With the subsequent lines in I just get more of the same type of warnings. The test itself completes and passes. Commented Mar 23, 2021 at 20:39

1 Answer 1

2

Here are several things you can do:

  • The warning tells you that your app continues after your test completed, and thus other things happen during the run that you didn't check in your test and didn't wait for. So if you're happy with what you are testing, be warned and keep it like that.
  • You can make your app a bit more testable by giving it options. For example you can have an optional prop indicating the amount of time to wait in timers, so that you can put it to 0 or a small number in your tests.
  • You can mock timers:
beforeEach(jest.useFakeTimers);
afterEach(jest.useRealTimers);

test("your game", async () => {
  const { getAllByRole } = render(<Game />);
  // [...]
  const firstItem = getAllByRole("listitem")[0]
  userEvent.click(firstItem)
  act(() => jest.advanceTimersByTime(5000));
  // [...]
  act(() => jest.advanceTimersByTime(5000));
  // [...]
});
Sign up to request clarification or add additional context in comments.

1 Comment

So I wrote a test fixture to make the game way shorter and I exended my test to play all the way through to the end. Once I had done that putting the advanceTimers functions in an "act" function did the trick. Thanks very much for your help!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.