Norayr Ghukasyan
Norayr Ghukasyan

Reputation: 1408

Jest assert for an element not to be in the document after `n` seconds

I am testing my component's error state after fetching a request. When the request fails, I update the component state to show the error message, and after 3000ms I update the state in a useEffect to hide the error.

import { render } from "@testing-library/react";
import { waitFor } from "@testing-library/dom";
import userEvent from '@testing-library/user-event';

test('Throws an error for wrong credentials', async () => {
const {
  getByText,
  getByRole,
  getByLabelText,
} = render(<Login />);

expect(getByText(/Admin login/i)).toBeInTheDocument();

const emailField = getByLabelText('Admin email');
const passwordField = getByLabelText('Password');
const button = getByText('Sign in');

userEvent.type(emailField, '[email protected]');
userEvent.type(passwordField, 'Wrong666!!!');
userEvent.click(button);

expect(getByRole('progressbar')).toBeInTheDocument();

jest.useFakeTimers();
jest.spyOn(window, 'setTimeout');

await waitFor(() => expect(getByRole('alert')).toHaveTextContent('These credentials do not match our records.'));
await waitFor(() => expect(getByRole('alert')).not.toBeInTheDocument());
});

Jest throws an error for the error element to be in the document for the assertion await waitFor(() => expect(getByRole('alert')).not.toBeInTheDocument());

expected document not to contain element, found <div class="sc-bczRLJ iXcWnZ text " color="gray-primary" role="alert" title="">These credentials do not match our records.</div> instead

Here is my Login component.

import { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { useDispatch } from 'react-redux';

import { Text } from 'core/components/text';

import { login } from 'reducers/auth';

export const Login = () => {
  const navigate = useNavigate();
  const dispatch = useDispatch<SharedDispFn>();

  const [loginError, setLoginError] = useState('');

  const onSignIn = async () => {
    const loginResponse = await dispatch(login(loginData));

    if (!loginResponse.status) {
      setLoginError(loginResponse.message);
    } else {
      navigate('/dashboard');
    }
  };

  useEffect(() => {
    const timer = setTimeout(() => {
      setLoginError('');
    }, 3000);
    return () => clearTimeout(timer);
  }, [loginError]);

  return (
    <Form checkFormValidation={setFormIsValid} onSubmit={onSignIn}>
      {loginError && (
        <Text color="gray-primary" role="alert" children={loginError} />
      )}
    // Rest of the code ...
    </Form>
  );
};

In Jest documentation they say How to mock setTimeout, but the fact is it doesn't work for me.

Upvotes: 1

Views: 1107

Answers (1)

cdallava
cdallava

Reputation: 71

Spying on setTimeout doesn’t wait for the function to execute. jest.runAllTimers(); is what you’ll utilize to fast forward any timers in your code. Check out the “Run All Timers” section in the link you sent to see the documentation on how to use it.

Also, once you get the async code to work, using getByRole will throw an error trying to get an element that doesn’t exist. Instead use queryByRole, which won’t fail if an element is not found. That documentation can be found here: https://testing-library.com/docs/queries/about/

Upvotes: 2

Related Questions