Why should we not use document.querySelector() while testing React components.
Why do we need to target the element?
While writing a unit test case for the react component, first we need to target the smaller sub-components and write the tests for the sub-components which might include:
- checking the existence in the document
- perform various events on them if required as per the written logic
- checking if the output is as expected, etc.
there are various ways to target the element provided to the user by different libraries. One of them is a JS query: document.querySelector('')
`, which we are going to talk about in this article.
Why do we want to avoid document.querySelector()?
As the RTL philosophy suggests, we want to avoid selecting elements by classNames, ids, or even test ids, because that is not how the user would find the element on the screen.
querySelector
as a query strategy is vulnerable to DOM changes or if we are basing the selectors on CSS Styling, the CSS Styling locators can change independently of the functionality and we may find functional-based tests failing due to styling changes.
Using `screen` and avoiding container:
The base queries from DOM Testing Library require you to pass a container
as the first argument. Most framework implementations of Testing Library provide a pre-bound version of these queries when you render your components with them which means you do not have to provide a container. As explained in the following example.
While using querySelector
we have to make an extra container:
Example:
// HTML to be tested
<body>
<div id="app">
<label for="username-input">Username</label>
<input id="username-input" />
</div>
</body>
Required to create a container
and then pass that as the first argument. 👇
import { getByLabelText } from '@testing-library/dom';// Need to provide a container:
const container = document.querySelector('#app')
const inputNode2 = getByLabelText(container, 'Username')
Instead of this 👆 , we can use screen
from '@testing-library/dom’
👇
import { screen, getByLabelText } from '@testing-library/dom';
// With screen:
const inputNode1 = screen.getByLabelText('Username')
The workaround: (not querySelector, then what?)
Using querySelector as an escape hatch to query by class or id is not recommended because they are invisible to the user. Use a testid
if you have to but that too should be kept as a last resort to target an element because:
- The user cannot see (or hear) these, so this is only recommended for cases where you can’t match by role or text or it doesn’t make sense (e.g. when the text is dynamic)
- We have various other queries that can be used instead of
testid
that are accessibility friendly as well. Example: getByRole()
: (Highly recommended) This can be used to query every element that is exposed in the accessibility tree. With thename
option you can filter the returned elements by their accessible name. (if you can’t, it’s possible your UI is inaccessible). E.g:getByRole(‘button’, {name: /submit/i})
You may check the list of the roles here.getByLabelText
,getByPlaceholderText
,getByText
,getByDisplayValue
.
Also some semantic queries such as:
getByAltText
If your element is one that supportsalt
text (img
,area
,input
, and any custom element), then you can use this to find that element.getByTitle
: The title attribute is not consistently read by screen readers and is not visible by default for sighted users.
Helper extensions:
For targetting the elements you may use the extension:
- Testing Library: which query: Finds the suggested query to use in your tests, you may copy queries from any web page.
- Testing Playground: Adds Testing-Playground to the Chrome Developer Tools. Testing Playground is an extension that helps you find the best queries to select elements for the open-source Testing-Library family. It allows you to inspect the element hierarchies in the Chrome Developer Tools, and provides you with suggestions on how to select them while encouraging good testing practices. You will get two new tabs in your Chrome DevTools: “🐸 Testing Playground” as a tab, and as a side panel under the “Elements” tab.