'React Testing Library cleanup not working in Jest's describe bocks

I have some work in progress tests, this is working as expected:

describe("Parent", () => {
  afterEach(() => {
    cleanup();
    jest.resetModules();
  });

  describe("Test 1", () => {
    const wrapper = render(
      <MockProvider>
        <MyComponent />
      </MockProvider>
    );

    test("1 ", () => {
      expect(wrapper.baseElement).toMatchSnapshot();
      expect(wrapper.getByText("Apply").disabled).toBe(true);
    });
  });

  describe("Test 2", () => {
    test("1 ", () => {
      const wrapper = render(
        <MockProvider>
          <MyComponent />
        </MockProvider>
      );
      console.log(wrapper.getByText("Apply").disabled);
      expect(1).toBe(1);
    });
  });
});

However when I move the 2nd render function out of the test it errors:

describe("Parent", () => {
  afterEach(() => {
    cleanup();
    jest.resetModules();
  });

  describe("Test 1", () => {
    const wrapper = render(
      <MockProvider>
        <MyComponent />
      </MockProvider>
    );

    test("1 ", () => {
      expect(wrapper.baseElement).toMatchSnapshot();
      expect(wrapper.getByText("Apply").disabled).toBe(true);
    });
  });

  describe("Test 2", () => {
    const wrapper = render(
      <MockProvider>
        <MyComponent />
      </MockProvider>
    );
    test("1 ", () => {
      console.log(wrapper.getByText("Apply").disabled);
      expect(1).toBe(1);
    });
  });
});

The error Im getting is

Found multiple elements with the text: Apply

I can see in the console that the component is being rendered twice, so I think the cleanup function must not be working properly in regards to the describe block. This is weird as we already have Enzyme tests and the setup and tear down works fine for those.



Solution 1:[1]

To understand this we need to understand a little bit about how Jest runs our tests and how React Testing Library renders our components.


Jest

Consider the code below and try to guess what the output will be:

describe('First describe', () => {
  console.log('First describe');

  it('First test', () => {
    console.log('First test');
  });
});

describe('Second describe', () => {
  console.log('Second describe');

  it('Second test', () => {
    console.log('Second test');
  });
});

Output (hover to see):

First describe
Second describe
First test
Second test

Note that all describe methods were initialised before the tests started to run.

This should already give you an idea of the problem, but let's now look at RTL.


React Testing Library

Consider the code below and try to guess what the DOM will look like in the console:

const Greeting = () => 'Hello world';

describe('First describe', () => {
  const wrapper = render(<Greeting />);

  it('First test', () => {
    console.log(wrapper.debug());
  });
});

describe('Second describe', () => {
  render(<Greeting />);
});

Output (hover to see):

<body> <div>Hello world</div> <div>Hello world</div> </body>

When we don't specify a base element to the render function, it always uses the same document.body

The <div> elements wrapping Hello world are added by RTL when we don't specify a custom container.

All RTL queries are bound to the base element by default--document.body in this case.

Therefore,

getByText('Hello world'); // will find two elements and throw


This is how the code in RTL looks like for the render function. (semi-pseudo code)

if(!baseElement) {
  baseElement = document.body // body will be shared across renders
}
if(!container) {
  baseElement.appendChild(document.createElement('div')) // wraps our component
}
ReactDOM.render(component, container)

return {  container, baseElement, ...getQueriesForElement(baseElement)  }


To solve this issue

Do one of the following:

  1. call render inside the it or test methods
  2. specify the container in the queries
  3. specify a diferent base element for each render

Solution 2:[2]

Another Quick fix for this could be wrapping your component with "React.Fragment" before passing it to render()

test ('should find text in <Component />', () => {
   const {getByText} = render (<><Component /></>)
   const divElement = getByText (/Find Text/i)
   expect (divElement).toBeInTheDocument ()
})

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Doug
Solution 2 Emmanuel Daniel