2

I am encountering an issue in my Vitest testing setup where the Catalog component is unexpectedly rendered in the second test, even after applying cleanup with @testing-library/react's cleanup function.

I have a set of React component tests using React Testing Library. The tests are designed to render different components within a BrowserRouter. The first test (Catalog link works) clicks on a "Check the catalog" link (In the Home component) which renders the Catalog component and performs some assertions. The second test (Register link works) clicks on a "Register" link again in the home and asserts elements within the Register component.

However, despite using cleanup after each test and awaiting its completion, the second test appears to inherit the state or rendering of the Catalog component from the first test. I've tried various approaches, including using await cleanup() and removing the cleanup altogether, but the issue persists.

Here's my test setup:

import { afterEach, vi } from 'vitest';
import { cleanup } from '@testing-library/react';
import '@testing-library/jest-dom/vitest';

import { JSDOM } from 'jsdom';
const { window } = new JSDOM();
global.window = window;
// Mock the alert function
window.alert = vi.fn();

afterEach(() => {
  cleanup();
});


Here are the tests:

it('Catalog link works', async () => {
        render(<BrowserRouter>
            <App>
                <Home>
                </Home>
            </App>
        </BrowserRouter>);

        const main = screen.getByRole('main');
        const catalogLink = within(main).getByRole('link', { name: 'Check the catalog' });

        userEvent.click(catalogLink);

        await waitFor(() => {
            const searchInput = screen.getByPlaceholderText('Name or location');
            const selectElements = screen.getAllByRole('combobox');
            expect(searchInput).toBeVisible();

            selectElements.forEach(el => expect(el).toBeVisible());
        });
    });

    it('Register link works', async () => {
        render(
            <BrowserRouter>
                <App>
                    <Home />
                </App>
            </BrowserRouter>
        );

        const main = screen.getByRole('main');
        const registerLink = within(main).getByRole('link', { name: 'Register' });

        console.log(registerLink);
        userEvent.click(registerLink);

        await waitFor(() => {
            ['Email', 'Password', 'Repeat password']
                .map((el) => screen.getByPlaceholderText(el))
                .forEach(el => expect(el).toBeVisible());
        });
    });

If I put a screen.debug(), I can clearly see that the Catalog component is rendered instead of the Home. So the Register link can't be reached.

I have attempted to isolate each test by using the cleanup function from @testing-library/react after each test. The cleanup function is expected to unmount the rendered component and clean up the DOM, ensuring a clean slate for each test.

I've also tried variations of the cleanup approach, such as removing cleanup altogether or using await cleanup() to make sure it completes before moving on to the next test.

I tried running the test by themselves and they seem to work. What am I doing wrong? If you need more information let me know.

2 Answers 2

0

I see what you are trying to do there, you can change your test setup as follows:

let BASE_URL = null;
afterEach(() => {
  // Cleaning the component and setting up the
  if (!BASE_URL) BASE_URL = window.location.origin;

  delete window.location;
  window.location = new URL(BASE_URL);
  cleanup();
});

Being that said: I recommend you to instead of doing that [above] — switch to MemoryRouter instead (as you can see reactrouter's documentation itself recommens it, see the "Tip" there).

If you do this instead, you don't need to change the location with that trick

render(
  <MemoryRouter>
    <App>
      <Home></Home>
    </App>
  </MemoryRouter>
);

NOTE: from your original code, you actually don't need the following to achieve this

/* All this code from your snipped is not needed */
import { JSDOM } from 'jsdom';
const { window } = new JSDOM();
global.window = window;
// Mock the alert function
window.alert = vi.fn();
Sign up to request clarification or add additional context in comments.

Comments

0

The below worked for me. You need to have the afterEach method and the related tests wrapped in a describe method

describe('TestSuiteName', () => {
  afterEach(() => {
    cleanup();
  });


  test('TestName/Description', () => {

    render(
      <Component />
    );

    // Assertions
  });
});

Comments

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.