We use Testing Library to test our React UI pages and components.

For example, we may have the following HelpFaqsTile component in a page:

const HelpFaqsTile = () => (
  <CardWithStyleOverrides data-testid="help-faqs-tile">
    <DashboardImage
      src={require('../../assets/cartoon-lifebelt.png')}
      alt="a floating lifesaver"
    />
    <TileContentWrapper>
      <h4>Need some help?</h4>
      <p>Our FAQs are a lifesaver</p>
      <Button as={Link} to="/help">
        Search FAQs
      </Button>
    </TileContentWrapper>
  </CardWithStyleOverrides>
);

export default HelpFaqsTile;

Let suppose we want to test whether “the ‘Help Faqs’ tile provides a link with the text Search FAQs”:

import { act, render } from '@testing-library/react';

await render(
  <PageWhichContainsTheHelpFaqsTile />
);

const buttonToFaq = screen.getByRole('link', {
  name: 'Search FAQs',
});

expect(buttonToFaq).toBeInTheDocument();

*ByRole methods like screen.getAllByRole('link', {name: 'Search FAQs'}) can be slow due to the way in which they search through the entire DOM tree looking for nodes matching the role type, and optionally a variety of attributes (label, aria-label, etc. depending on additional criteria, here name). In contrast, the more performant *ByText and *ByLabelText methods look for text nodes and nodes with more limited attributes (label and aria-label only) respectively. So, we could use screen.getByText('Search FAQs') instead:

import { act, render } from '@testing-library/react';

await render(
  <PageWhichContainsTheHelpFaqsTile />
);

const buttonToFaq = screen.getByText('Search FAQs');

expect(buttonToFaq).toBeInTheDocument();

They are not like for like replacements however. Let’s suppose we want to test “the ‘Help Faqs’ tile provides a link which directs the user to our FAQ’s”

import { act, render } from '@testing-library/react';

await render(
  <PageWhichContainsTheHelpFaqsTile />
);

const buttonToFaq = screen.getByRole('link', {
  name: 'Search FAQs',
});

expect(buttonToFaq).toBeInTheDocument();
expect(buttonToFaq).toHaveAttribute('href', '/help');

We cannot easily replace *ByRole because it actually finds the link node which contains the text ‘Search FAQs’ and the link node is the one with the href attribute we want to test. Whereas *ByText finds the text node with text ‘Search FAQs’ and in our example these are not the same, as our Button component renders to:

<a class="Container" href="/help">
  <span class="InnerFlexContainer">
    Search FAQs
  </span>
</a>

In this case we can instead contextualise the *ByRole method by selecting a parent node and replacing screen.getByRole with within(parentNode):

import { act, render } from '@testing-library/react';

const tile = screen.queryByTestId('help-faqs-tile');

expect(tile).toBeInTheDocument();

const buttonToFaq = within(tile).getByRole('link', {
  name: 'Search FAQs',
});

expect(buttonToFaq).toBeInTheDocument();
expect(buttonToFaq).toHaveAttribute('href', '/help');

Replacing *ByRole with *ByText and *ByLabelText or simply contextualising things and replacing screen.getByRole with within(parentNode).getByRole has allowed us to improve the performance of our tests. In the instances we tested, *ByLabelText gave us a 95% performance improvement (from 320ms to 16ms).