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).