React Testing Library query requires additional waitFor before succeeding

Viewed 1344

I'm using React Testing Library to test a component that fetches data in componentDidMount via redux thunks, with a MSW JS-mocked backend.

My test is essentially:

  1. render app
  2. wait for the component's "loading state/spinner" to clear (after all the redux thunk stuff succeeds), which is signified by the presence of a button with the text "Create Card"
  3. click the "Create Card" button
  4. wait for redux thunk stuff to succeed in creating the card, similarly to #2
  5. query the component for evidence of the created card
const history = createMemoryHistory()
history.push(`/${DECK_ID}`)

const { getByText } = render(
  <Provider store={store}>
    <Router basename="/games" history={history}>
      <App />
    </Router>
  </Provider>
)

await waitFor(() => getByText('Create Card'))
fireEvent.click(getByText('Create Card'))
await waitFor(() => getByText(`Id: ${NEW_CARD_TYPE_ID}`))

The weird thing is that 'Create Card' isn't found:

TestingLibraryElementError: Unable to find an element with the text: Create Card...
...
      124 |     await waitFor(() => getByText('Create Card'))
    > 125 |     fireEvent.click(getByText('Create Card'))

But it IS found when I either put an additional (redundant) waitFor before it, like:

await waitFor(() => getByText('Create Card'))
await waitFor(() => getByText('Create Card'))
fireEvent.click(getByText('Create Card'))
await waitFor(() => getByText(`Id: ${NEW_CARD_TYPE_ID}`))

... or wrap the click action in its own waitFor, like this:

await waitFor(() => getByText('Create Card'))
await waitFor(() => {
  fireEvent.click(getByText('Create Card'))
})
await waitFor(() => getByText(`Id: ${NEW_CARD_TYPE_ID}`))

And what's even weirder, when I add an assertion at the end, I start getting Warning: Encountered two children with the same key errors from the test. I never called the click event twice, but it appears as though more than 1 card was created:

await waitFor(() => getByText('Create Card'))
await waitFor(() => {
  fireEvent.click(getByText('Create Card'))
})
await waitFor(() => getByText(`Id: ${NEW_CARD_TYPE_ID}`))
expect(getByText(`Id: ${NEW_CARD_TYPE_ID}`).textContent).toBe(`Id: ${NEW_CARD_TYPE_ID}`)

Anyone have any idea why this might be happening? The example in the Testing Library link shows that you can mix non-async events with async waiting, so I'm not sure what's up.

Thanks

0 Answers
Related