Nathan Arthur
Nathan Arthur

Reputation: 9096

How to wait to assert an element never appears in the document?

I want to assert that an element never appears in my document. I know I can do this:

import '@testing-library/jest-dom/extend-expect'

it('does not contain element', async () => {
    const { queryByText } = await render(<MyComponent />);
    expect(queryByText('submit')).not.toBeInTheDocument();
});

But in my case I need to wait to ensure that the element isn't added after a delay. How can I achieve this?

Upvotes: 21

Views: 30926

Answers (5)

kordaris
kordaris

Reputation: 1

Instead of using waitFor() helper, you can first await for another element using findByText() and expect to be in the document. Also, it is a better practice to use screen.

  import {render, screen} from '@testing-library/react'
    
  it('does not contain element', async () => {
    render(<MyComponent />);
    const anotherElement = await screen.findByText("another_element");
    const submit = screen.queryByText("submit");
    
    expect(anotherElement).toBeInTheDocument();
    expect(submit).not.toBeInTheDocument();
  });

Upvotes: 0

Gerson Diniz
Gerson Diniz

Reputation: 1289

An edge case alternative would be:

export const expectNever = async (callable: () => unknown) => {
  try {
    await waitFor(callable);
  } catch {
    return true;
  }
  throw new Error('Occurred what should not occur!');
};

and use it like:

await expectNever(() => {
    expect(
      screen.queryByText('should not be rendered')
    ).toBeInTheDocument();
  });

Upvotes: 0

Andreas Presthammer
Andreas Presthammer

Reputation: 1990

Consider using waitForElementToBeRemoved documented here: https://testing-library.com/docs/guide-disappearance/#waiting-for-disappearance

Upvotes: 0

LDK
LDK

Reputation: 379

We use plain JavaScript and the expectNever function from @Nathan throws an error:

Error: expect(received).rejects.toEqual()
Matcher error: received value must be a promise

I modified it to look and feel more like waitFor and this works:

const waitForNeverToHappen = async (callable) => {
    await expect(waitFor(callable)).rejects.toEqual(expect.anything())
}
    
await waitForNeverToHappen(() => expect(screen.getByText('submit')).toBeInTheDocument())

Upvotes: 7

Nathan Arthur
Nathan Arthur

Reputation: 9096

There are two ways to do this, both involving react-testing-library's async helper function waitFor.

The first and simpler method is to wait until something else happens in your document before checking that the element doesn't exist:

import '@testing-library/jest-dom/extend-expect'

it('does not contain element', async () => {
    const { getByText, queryByText } = await render(<MyComponent />);

    await waitFor(() => expect(getByText('something_else')).toBeInTheDocument());

    expect(queryByText('submit')).not.toBeInTheDocument();
});

You can use the same strategy with any valid Jest assertion:

import '@testing-library/jest-dom/extend-expect'
import myFunc from './myFunc'

it('does not contain element', async () => {
    const { getByText, queryByText } = await render(<MyComponent />);

    await waitFor(() => expect(myFunc).toBeCalled());

    expect(queryByText('submit')).not.toBeInTheDocument();
});

If there isn't any good assertion you can use to wait for the right time to check an element does not exist, you can instead use waitFor to repeatedly check that an element does not exist over a period of time. If the element ever does exist before the assertion times out, the test will fail. Otherwise, the test will pass.

import '@testing-library/jest-dom/extend-expect'

it('does not contain element', async () => {
    const { getByText } = await render(<MyComponent />);

    await expect(async () => {
        await waitFor(
            () => expect(getByText('submit')).toBeInTheDocument();
        );
    }).rejects.toEqual(expect.anything());
});

You can adjust the amount of time waitFor will keep checking and how frequently it will check using the timeout and interval options. Do note, though, that since this test waits until waitFor times out for the test to pass, increasing the timeout option will directly increase the time this test takes to pass.

And here is the helper function I wrote to avoid having to repeat the boilerplate:

export async function expectNever(callable: () => unknown): Promise<void> {
    await expect(() => waitFor(callable)).rejects.toEqual(expect.anything());
}

Which is then used like so:

it('does not contain element', async () => {
  const { getByText } = await render(<MyComponent />);

  await expectNever(() => {
    expect(getByText('submit')).toBeInTheDocument();
  });
});

Upvotes: 27

Related Questions