Search code examples
reactjsreact-testing-libraryjsdomvitest

How can I run tests in a single file in parallel when using 'screen' from Testing Library?


I am using React Testing Library with jsdom and Vitest. I am trying to run multiple unit tests in one file but the screen variable appears to have output of all render calls, from all tests, resulting in duplicates. screen is recommended per:

However, if I use the obsolete approach of explicit container I do not run into this issue.

How can I use screen and run tests in parallel, making sure each one has its own isolated DOM?

This works, both tests pass (they run sequentially), but it does not use screen:

import { render, getByText } from '@testing-library/react'
import { describe, test } from 'vitest'

describe('Parallelism test', () => {
  test('Render 1', () => {
    const renderResult = render(<div>foo</div>)
    getByText(renderResult.container, 'foo')
  })

  test('Render 2', () => {
    const renderResult = render(<div>foo</div>)
    getByText(renderResult.container, 'foo')
  })
})

Parallelism test working

But if I use screen with sequentially running tests, I run into a failure:

import { render, screen } from '@testing-library/react'
import { describe, test } from 'vitest'

describe('Parallelism test', () => {
  test('Render 1', () => {
    render(<div>foo</div>)
    screen.getByText('foo')
  })

  test('Render 2', () => {
    render(<div>foo</div>)
    screen.getByText('foo')
  })
})

Parallelism not working

Failure details

Related question:


Solution

  • In summary:

    • To make tests work sequentially, I was missing the cleanup invocation after each test.
    • If using jsdom via the vitest environment, it is not possible to avoid tests in a single file reusing it. Possibly setting up jsdom directly would solve this. Some ideas in this answer.
    • Tests in separate files by default run in parallel with isolated environments, including jsdom DOM.

    I believe I got it work with getByText(renderResult.container, 'foo') because it is not correct usage. Instead, I should have used renderResult.getByText('foo') which runs into the same problem. The renderResult.container approach cannot find e.g. pop-ups. Furthermore, the tests in the examples I provided are run sequentially, because vitest's --sequence.concurrent defaults to false.

    To actually make it work at least sequentially, the cleanup must happen.

    The tooling was trying to discourage me from adding any cleanup by ESLint warnings:

    However, once I ignored all the ESLint warnings and added the cleanup, tests now work when run sequentially:

    import { render, screen, cleanup } from '@testing-library/react'
    import { describe, test, afterEach } from 'vitest'
    
    describe('Parallelism test', () => {
      afterEach(() => {
        console.log('afterEach cleanup')
        cleanup()
      })
    
      test('Render 1', async () => {
        console.log('r1')
        render(<div>foo</div>)
        screen.getByText('foo')
        await delay(1000)
      })
    
      test('Render 2', async () => {
        console.log('r2')
        render(<div>foo</div>)
        screen.getByText('foo')
        await delay(1000)
      })
    })
    
    async function delay(ms: number): Promise<void> {
      return new Promise((resolve) => setTimeout(resolve, ms))
    }
    

    However, when run with --sequence.concurrent=true it fails the usual way.

    The good news is that tests in separate files run in parallel by default per fileParallelism being set to true and their environments are isolated per poolOptions.threads.isolate being set to true.