Timed out in waitForElementToBeRemoved error in RTL

Viewed 3773

I have a basic login form made using Formik, just trying to write an RTL and jest code to fill out the details, submit the form, and navigate to the Home page. Therefore, wrote the following:

 it('Fill the form and login', async () => {
    render(<Login />)

    await userEvent.type(screen.getByTestId('emailInput'), 'neeraj@gmail.com')
    await userEvent.type(screen.getByTestId('passwordInput'), 'neeraj')
    await userEvent.click(screen.getByTestId('submitBtn'))

    expect(window.location.pathname).toBe('/')
  })

The above test is getting passed but getting the classic act error.

When testing, code that causes React state updates should be wrapped into act(...):
    
    act(() => {
      /* fire events that update state */
    });
    /* assert on the output */

So, referred to this blog post and used waitForElementToBeRemoved function as suggested but now the test is getting failed with a timeout error.

Updated test case with waitForElementToBeRemoved

it('Fill the form and login', async () => {
    render(<Login />)

    await userEvent.type(screen.getByTestId('emailInput'), 'neeraj@gmail.com')
    await userEvent.type(screen.getByTestId('passwordInput'), 'neeraj')
    await userEvent.click(screen.getByTestId('submitBtn'))

    expect(window.location.pathname).toBe('/')
    await waitForElementToBeRemoved(screen.getByTestId('emailInput'))
  })

Error:

 Timed out in waitForElementToBeRemoved.

    Ignored nodes: comments, <script />, <style />
    <html>
      <head />
      <body>
        <div>
          <div>
            <div
              data-testid="loginCard"
            >
              <form
                data-testid="loginForm"
              >
                <div>
                  <label
                    for="email-input"
                  >
                    Email
                  </label>
                  <input
                    data-testid="emailInput"
                    id="email-input"
                    name="email"
                    type="email"
                    value="neeraj@gmail.com"
                  />
                </div>
                <div>
                  <label
                    for="pass-input"
                  >
                    Password
                  </label>
                  <input
                    data-testid="passwordInput"
                    id="pass-input"
                    name="password"
                    type="password"
                    value="neeraj"
                  />
                </div>
                <button
                  data-testid="submitBtn"
                  type="submit"
                >
                  Submit
                </button>
              </form>
            </div>
          </div>
        </div>
      </body>
    </html>

      19 |
      20 |     expect(window.location.pathname).toBe('/')
    > 21 |     await waitForElementToBeRemoved(screen.getByTestId('emailInput'))
         |           ^
      22 |   })
      23 | })
      24 |

      at waitForElementToBeRemoved (node_modules/@testing-library/dom/dist/wait-for-element-to-be-removed.js:22:24)
      at Object.<anonymous> (tests/login.test.js:21:11)

Tried to wrap those three userEvent s in act function but getting the same timeout error, couldn't figure out where I am going wrong.

Code Sandbox link

1 Answers

I managed to pass the test case with no act warnings.

Since you are calling an setTimeout which is an asynchronous function when the user clicks on the submitBtn button, therefore you have to wait for the promise to resolve.

import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import React from 'react'

import Login from './../pages/login'

describe('Login Page', () => {
  it('renders without crashing', () => {
    render(<Login />)
    expect(screen.getByTestId('loginCard')).toBeInTheDocument()
  })
  
  it('Fill the form and login', async () => {
    render(<Login />)

    userEvent.type(screen.getByLabelText(/email/i), 'neeraj@gmail.com')
    userEvent.type(screen.getByLabelText(/password/i), 'neeraj')
    userEvent.click(screen.getByTestId('submitBtn'))

    await waitFor(() => expect(window.location.pathname).toBe('/'))
  })
})

You could also do it using async act on the click event which will going to be wait for the promise to resolve and state updates to complete.

import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import React from 'react'

import Login from './../pages/login'

describe('Login Page', () => {
  it('renders without crashing', () => {
    render(<Login />)
    expect(screen.getByTestId('loginCard')).toBeInTheDocument()
  })
  
  it('Fill the form and login', async () => {
    render(<Login />)

    userEvent.type(screen.getByLabelText(/email/i), 'neeraj@gmail.com')
    userEvent.type(screen.getByLabelText(/password/i), 'neeraj')
    await act(async () => userEvent.click(screen.getByTestId('submitBtn')))

    expect(window.location.pathname).toBe('/')
  })
})
Related