Miłosz Orzeł

.net, js, html, arduino, java... no rants or clickbaits.

Filtering DOM Dump in React Testing Library

PRINTING DOM

Last post was about suppressing chosen console warnings while executing tests. This one is also about making tests output less noisy...

React Testing Library has a nice feature for outputting well formatted container/screen DOM when get or find queries (getByText, findByRole etc.) fail.

Document structure dump is limited to a value controlled by DEBUG_PRINT_LIMIT setting (default: 7000) but even smaller dumps can become inconvenient to use. That is especially true if part of the printed markup doesn't exactly belong to tested component but is somehow injected by external a piece of code. I've had such issue with a dependency that created a container with SVG icons, which completely flooded DOM dumps, making them practically useless. I can't post the actual code here (work stuff) but I've made a little app to simulate the issue.

 

THE PROBLEM

Let's say we have such component:

import React from 'react';
import './InjectLoremIpsum';

const MyComponent = () => (
    <div>
        Hello from MyComponent!
    </div>
);

export default MyComponent;

and we have a test (obviously failing):

test('checking if element filter works', () => {
    render(<MyComponent />);
    expect(screen.getByText('Random stuff that does not exist...')).toBeInTheDocument();
});

so we expect to see a nice error message along with screen's DOM dump containing MyComponent markup:

 FAIL  src/MyComponent.test.js
  ● checking if element filter works

    TestingLibraryElementError: Unable to find an element with the text: Random stuff that does not exist.... This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.

    Ignored nodes: comments, <script />, <style />
    <body>
      <div>
        <div>
          Hello from MyComponent!
        </div>
      </div>
    </body>

But there's a problem! MyComponent imports a module (InjectLoremIpsum) that injects elements to document body and this stuff gets printed too:

FAIL  src/MyComponent.test.js
  ● checking if element filter works

    TestingLibraryElementError: Unable to find an element with the text: Random stuff that does not exist.... This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.

    Ignored nodes: comments, <script />, <style />
    <body>
      <div
        id="injected-stuff"
      >
        <div
          style="display: none;"
        >
          <p>
            Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas euismod, purus eu mollis efficitur, tellus lectus laoreet leo, ut tincidunt magna felis id tortor. Donec ut mi feugiat, pellentesque libero id, auctor eros. Proin tincidunt tristique orci ac vehicula. Praesent consectetur auctor elit, eu faucibus neque placerat ut. Curabitur ullamcorper venenatis vulputate. Ut viverra at lectus et placerat. Ut efficitur sodales elit, quis faucibus dolor interdum vitae. Ut ultrices sapien vitae ligula laoreet, non facilisis tortor vulputate. Mauris non magna fringilla, placerat purus eget, accumsan sapien. Nunc sit amet elementum libero. Pellentesque et vulputate tellus.
          </p>
          <p>
            Nam ac est rhoncus felis bibendum mattis quis eget neque. Phasellus rhoncus et ex vitae mollis. Mauris ultrices non metus ac lacinia. Nulla ut porta nulla. Aliquam pellentesque euismod orci, sed gravida tortor vestibulum id. Nulla at lectus molestie, consectetur turpis quis, viverra massa. Aliquam ultricies, nulla id varius varius, lorem nibh aliquet odio, ac bibendum erat ante quis nisi. Aenean quis hendrerit justo, id ultricies mi. Donec volutpat lacus placerat nisl malesuada dictum. Pellentesque tincidunt dolor et arcu elementum, interdum volutpat erat vehicula.
          </p>
          <p>
            Vivamus ut ultrices odio, sed consectetur diam. Praesent pharetra et massa vel elementum. Quisque tortor massa, pharetra vitae sapien quis, ullamcorper auctor quam. Nam scelerisque molestie leo, a cursus urna. Praesent massa erat, ullamcorper ac arcu sed, tincidunt imperdiet dui. Nulla facilisi. Nulla erat augue, sollicitudin nec eros ut, rhoncus lacinia leo. Aliquam in sem massa. Sed dictum erat erat, ac finibus est pretium id. Proin sit amet odio sem. Nulla vehicula nulla viverra dignissim ornare. Phasellus vitae lectus vitae nisi condimentum facilisis. Nullam eu nulla tellus. Aliquam quis diam nec lectus volutpat consectetur et sit amet lacus. Nulla condimentum nibh quis quam vestibulum imperdiet.
          </p>
          <p>
            Suspendisse tristique quis eros vitae gravida. Quisque at lectus quam. Mauris aliquet turpis a erat aliquet auctor. Curabitur maximus, ipsum et accumsan blandit, urna dui scelerisque dui, at dapibus enim odio ut ligula. Vestibulum ligula sapien, fermentum id erat vitae, accumsan condimentum felis. Integer fringilla nibh in purus posuere scelerisque. Mauris et erat tortor.
          </p>
          <p>
            Praesent in cursus neque. Pellentesque feugiat egestas nibh, nec faucibus nisi lobortis vitae. Phasellus leo mauris, commodo eget urna condimentum, eleifend bibendum mi. Morbi blandit egestas cursus. Phasellus a elit consequat, euismod tortor ac, elementum est. Ut condimentum mattis mi, at blandit nisl vehicula in. Proin imperdiet ullamcorper ligula non consequat. Proin eu rhoncus nibh, eget tristique nulla. Fusce aliquet neque eu quam imperdiet scelerisque. Integer vitae consectetur diam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi eget ligula at dolor pretium maximus. Integer sit amet ipsum id justo elementum dignissim non quis purus. Ut fringilla venenatis felis non volutpat. Praesent et massa in nibh posuere gravida vel non mauris.
          </p>
          <p>
            Phasellus auctor, ante sed egestas posuere, justo erat maximus ipsum, eget fringilla tortor nisl vitae nulla. Duis rutrum eros eget ante consectetur, ut blandit orci elementum. Nam maximus lacus sed elit elementum hendrerit. Phasellus lorem magna, hendrerit non pretium eget, scelerisque quis diam. Sed pulvinar quis felis at placerat. Quisque malesuada in est vitae viverra. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae;
          </p>
          <p>
            Fusce eget gravida lectus. Morbi fringilla ac dui sed varius. Suspendisse ac interdum massa, ac egestas enim. Donec non lacus eget elit efficitur ultrices. Vestibulum volutpat facilisis orci, id cursus nulla egestas ut. Aenean metus magna, rhoncus et lorem cursus, tempus aliquam nibh. Quisque non malesuada urna. Maecenas quis sodales odio. Cras tempor est velit, at laoreet augue aliquet a. Nullam scelerisque tincidunt elementum. Mauris felis odio, feugiat in nisl vel, maximus efficitur metus.
          </p>
          <p>
            Donec ut congue urna. Mauris sit amet facilisis nisl. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Proin dapibus rhoncus feugiat. Praesent rutrum nisi lorem, eget dapibus ipsum pellentesque non. In varius pretium mauris. Donec vitae suscipit quam. Praesent ac dapibus eros. Donec fermentum, leo in fringilla sagittis, felis dui auctor augue, vitae porttitor nibh augue sed diam. Vivamus ac sem vitae risus placerat ultricies. Nulla lobortis consequat molestie. Donec dolor ipsum, ultrices in sagittis in, dictum vel tellus. Fusce volutpat id enim ut rutrum. Nullam sit amet justo commodo, cursus erat non, facilisis arcu. Ut porttitor ipsum sapien, id finibus nisl molestie a.
          </p>
          <p>
            Sed urna lectus, ornare non sagittis ac, ullamcorper vitae diam. Nunc quis placerat leo. Praesent rhoncus nulla nec ipsum dapibus porta. Aliquam vestibulum metus metus, ut sagittis sem bibendum at. Integer placerat arcu erat, at congue risus convallis eget. Nullam tincidunt eget lacus ut laoreet. Nam consequat luctus velit, non facilisis magna. Nunc facilisis, enim sed efficitur varius, turpis nulla laoreet sem, ut sagittis orci diam in augue. Nunc iaculis neque metus, vel dictum sapien elementum nec. Quisque ullamcorper sem sit amet dictum sollicitudin. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi tincidunt fermentum iaculis. Nunc ullamcorper justo eget dignissim cursus. Donec sit amet est ex. Aliquam eget accumsan odio. Aenean eu iaculis lacus, egestas rhoncus lorem.
          </p>
          <p>
            Nunc in velit ac leo condimentum aliquam quis ut lorem. Sed sit amet pharetra metus. Nulla accumsan lorem ex, quis sodales felis mollis id. In venenatis tellus a magna tincidunt, a semper libero feugiat. Suspendisse at orci in elit vestibulum auctor. Ut mi nisl, tempor id scelerisque semper, viverra ac nisi. In quam risus, gravida tempor varius at, maximus id nisi. Nullam at ligula sapien. Nunc libero orci, dictum sit amet imperdiet non, ultricies et metus. Nunc vel elit imperdiet, volutpat dui venenatis, consequat arcu. Praesent in fringilla massa. Sed volutpat at turpis sit amet dapibus. Curabitur id nunc at libero tristique malesuada eget eget nulla. Phasellus eu nisi sem. Donec elementum ut velit vitae ultrices. Integer ullamcorper enim ac nisi sodales, pellentesque lobortis neque lacinia.
          </p>
        </div>
      </div>
      <div>
        <div>
          Hello from MyComponent!
        </div>
      </div>
    </body>

The div with "injected-stuff" id is there to simulate a piece of markup that gets included in the body and clutters the output of tested component. Like I mentioned, I've had a case when a crucial dependency created a container with very large amount of SVG elements. I couldn't just removed the dep, but I still wanted to have usable tests outputs...

 

THE SOLUTION

Fortunately RTL lets as define a custom implementation of getElementError function that returns errors for failing get* and find* calls. 

Here's an example config in setupTest.js file (standard file from CRA):

import '@testing-library/jest-dom';
import { configure, prettyDOM } from '@testing-library/dom';

configure({
    getElementError: (message, container) => {
        const customMessage = [
            message,
            prettyDOM(container, null, {
                filterNode: node => node.id !== 'injected-stuff'
            })
        ].join('\n\n');

        return new Error(customMessage);
    }
});

Notice the prettyDOM usage which provides filterNode function. If that function returns false, the node will not become a part of the pretty-printed DOM. We use it to filter out pesky injected-stuff div. With such configuration in place, the test output becomes usable again:

  FAIL  src/MyComponent.test.js
  ● checking if element filter works

    Unable to find an element with the text: Random stuff that does not exist.... This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.

    <body>
      <div>
        <div>
          Hello from MyComponent!
        </div>
      </div>
    </body>

 

IT'S NOT PERFECT

One drawback of providing custom getElementError config is that a stack trace included at the end of error will point to a line with return statement in getElementError instead of a line with failing assertion. This is not a big deal in practice because failing test message should make it pretty clear what went wrong.

The other issue is that default error reporting skips comments and scripts/styles tags and our custom implementation doesn't do it. I wanted to keep the example as simple as possible but you could put arbitrarily complex logic inside filterNode function (check out RTL source for hints).

Oh, and if you see such message:

pretty-format: Unknown option "filterNode".

make sure you are using latest @testing-library/react version. My project originally had v11.2.7 and I got this error but after updating to v12.0.0 everything went ok.

 

SAMPLE PROJECT

GitHub repo: https://github.com/morzel85/blog-post-react-testing-library-element-filter (React 17.0.2).

Run npm install followed by npm test to see how the config in setupTest.js tweaks DOM printing behavior when RTL test fails.