Skip to content

Commit 94f8d43

Browse files
committed
Re-organize 5c and recommend using querySelector only if necessary
1 parent 59fdc89 commit 94f8d43

File tree

2 files changed

+175
-226
lines changed

2 files changed

+175
-226
lines changed

src/content/5/en/part5c.md

Lines changed: 88 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,77 @@ test('renders content', () => {
204204

205205
Test fails if _getByText_ does not find the element it is looking for.
206206

207+
The _getByText_ command, by default, searches for an element that contains only the **text provided as a parameter** and nothing else. Let us assume that a component would render text to an HTML element as follows:
208+
209+
```js
210+
const Note = ({ note, toggleImportance }) => {
211+
const label = note.important
212+
? 'make not important' : 'make important'
213+
214+
return (
215+
<li className='note'>
216+
Your awesome note: {note.content} // highlight-line
217+
<button onClick={toggleImportance}>{label}</button>
218+
</li>
219+
)
220+
}
221+
222+
export default Note
223+
```
224+
225+
The _getByText_ method that the test uses does <i>not</i> find the element:
226+
227+
```js
228+
test('renders content', () => {
229+
const note = {
230+
content: 'Does not work anymore :(',
231+
important: true
232+
}
233+
234+
render(<Note note={note} />)
235+
236+
const element = screen.getByText('Does not work anymore :(')
237+
238+
expect(element).toBeDefined()
239+
})
240+
```
241+
242+
If we want to look for an element that <i>contains</i> the text, we could use an extra option:
243+
244+
```js
245+
const element = screen.getByText(
246+
'Does not work anymore :(', { exact: false }
247+
)
248+
```
249+
250+
or we could use the _findByText_ method:
251+
252+
```js
253+
const element = await screen.findByText('Does not work anymore :(')
254+
```
255+
256+
It is important to notice that, unlike the other _ByText_ methods, _findByText_ returns a promise!
257+
258+
There are situations where yet another form of the _queryByText_ method is useful. The method returns the element but <i>it does not cause an exception</i> if it is not found.
259+
260+
We could eg. use the method to ensure that something <i>is not rendered</i> to the component:
261+
262+
```js
263+
test('does not render this', () => {
264+
const note = {
265+
content: 'This is a reminder',
266+
important: true
267+
}
268+
269+
render(<Note note={note} />)
270+
271+
const element = screen.queryByText('do not want this thing to be rendered')
272+
expect(element).toBeNull()
273+
})
274+
```
275+
276+
Other methods also exist, such as [getByTestId](https://testing-library.com/docs/queries/bytestid/), which searches for elements based on id fields specifically created for testing purposes.
277+
207278
We could also use [CSS-selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors) to find rendered elements by using the method [querySelector](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector) of the object [container](https://testing-library.com/docs/react-testing-library/api/#container-1) that is one of the fields returned by the render:
208279

209280
```js
@@ -227,7 +298,7 @@ test('renders content', () => {
227298
})
228299
```
229300

230-
**NB:** A more consistent way of selecting elements is using a [data attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/data-*) that is specifically defined for testing purposes. Using _react-testing-library_, we can leverage the [getByTestId](https://testing-library.com/docs/queries/bytestid/) method to select elements with a specified _data-testid_ attribute.
301+
It is, however, recommended to search for elements primarily using methods other than the <i>container</i> object and CSS selectors. CSS attributes can often be changed without affecting the application's functionality, and users are not aware of them. It is better to search for elements based on properties visible to the user, for example, by using the _getByText_ method. This way, the tests better simulate the actual nature of the component and how a user would find the element on the screen.
231302

232303
### Debugging tests
233304

@@ -381,79 +452,52 @@ In our example, the mock function is a perfect choice since it can be easily use
381452

382453
### Tests for the <i>Togglable</i> component
383454

384-
Let's write a few tests for the <i>Togglable</i> component. Let's add the <i>togglableContent</i> CSS classname to the div that returns the child components.
385-
386-
```js
387-
const Togglable = props => {
388-
// ...
389-
390-
return (
391-
<div>
392-
<div style={hideWhenVisible}>
393-
<button onClick={toggleVisibility}>
394-
{props.buttonLabel}
395-
</button>
396-
</div>
397-
<div style={showWhenVisible} className="togglableContent"> // highlight-line
398-
{props.children}
399-
<button onClick={toggleVisibility}>cancel</button>
400-
</div>
401-
</div>
402-
)
403-
}
404-
```
405-
406-
The tests are shown below:
455+
Let's write a few tests for the <i>Togglable</i> component. The tests are shown below:
407456

408457
```js
409458
import { render, screen } from '@testing-library/react'
410459
import userEvent from '@testing-library/user-event'
411460
import Togglable from './Togglable'
412461

413462
describe('<Togglable />', () => {
414-
let container
415-
416463
beforeEach(() => {
417-
container = render(
464+
render(
418465
<Togglable buttonLabel="show...">
419-
<div className="testDiv" >
420-
togglable content
421-
</div>
466+
<div>togglable content</div>
422467
</Togglable>
423-
).container
468+
)
424469
})
425470

426-
test('renders its children', async () => {
427-
await screen.findAllByText('togglable content')
471+
test('renders its children', () => {
472+
screen.getByText('togglable content')
428473
})
429474

430475
test('at start the children are not displayed', () => {
431-
const div = container.querySelector('.togglableContent')
432-
expect(div).toHaveStyle('display: none')
476+
const element = screen.getByText('togglable content')
477+
expect(element).not.toBeVisible()
433478
})
434479

435480
test('after clicking the button, children are displayed', async () => {
436481
const user = userEvent.setup()
437482
const button = screen.getByText('show...')
438483
await user.click(button)
439484

440-
const div = container.querySelector('.togglableContent')
441-
expect(div).not.toHaveStyle('display: none')
485+
const element = screen.getByText('togglable content')
486+
expect(element).toBeVisible()
442487
})
443-
})
444488
```
445489
446-
The _beforeEach_ function gets called before each test, which then renders the <i>Togglable</i> component and saves the field _container_ of the returned value.
490+
The _beforeEach_ function gets called before each test, which then renders the <i>Togglable</i> component.
447491
448492
The first test verifies that the <i>Togglable</i> component renders its child component
449493
450494
```js
451-
<div className="testDiv">
495+
<div>
452496
togglable content
453497
</div>
454498
```
455499
456-
The remaining tests use the [toHaveStyle](https://www.npmjs.com/package/@testing-library/jest-dom#tohavestyle) method to verify that the child component of the <i>Togglable</i> component is not visible initially, by checking that the style of the <i>div</i> element contains _{ display: 'none' }_. Another test verifies that when the button is pressed the component is visible, meaning that the style for hiding it <i>is no longer</i> assigned to the component.
500+
The remaining tests use the _toBeVisible_ method to verify that the child component of the <i>Togglable</i> component is not visible initially, i.e. that the style of the <i>div</i> element contains _{ display: 'none' }_. Another test verifies that when the button is pressed the component is visible, meaning that the style for hiding it <i>is no longer</i> assigned to the component.
457501
458502
Let's also add a test that can be used to verify that the visible content can be hidden by clicking the second button of the component:
459503
@@ -470,8 +514,8 @@ describe('<Togglable />', () => {
470514
const closeButton = screen.getByText('cancel')
471515
await user.click(closeButton)
472516

473-
const div = container.querySelector('.togglableContent')
474-
expect(div).toHaveStyle('display: none')
517+
const element = screen.getByText('togglable content')
518+
expect(element).not.toBeVisible()
475519
})
476520
})
477521
```
@@ -711,7 +755,7 @@ test('<NoteForm /> updates parent state and calls onSubmit', () => {
711755
})
712756
```
713757
714-
The most flexible way of finding elements in tests is the method <i>querySelector</i> of the _container_ object, which is returned by _render_, as was mentioned [earlier in this part](/en/part5/testing_react_apps#searching-for-content-in-a-component). Any CSS selector can be used with this method for searching elements in tests.
758+
Sometimes, finding the correct element using the methods described above can be challenging. In such cases, an alternative is the method <i>querySelector</i> of the _container_ object, which is returned by _render_, as was mentioned [earlier in this part](/en/part5/testing_react_apps#searching-for-content-in-a-component). Any CSS selector can be used with this method for searching elements in tests.
715759
716760
Consider eg. that we would define a unique _id_ to the input field:
717761
@@ -750,75 +794,6 @@ const input = container.querySelector('#note-input')
750794
751795
However, we shall stick to the approach of using _getByPlaceholderText_ in the test.
752796
753-
Let us look at a couple of details before moving on. Let us assume that a component would render text to an HTML element as follows:
754-
755-
```js
756-
const Note = ({ note, toggleImportance }) => {
757-
const label = note.important
758-
? 'make not important' : 'make important'
759-
760-
return (
761-
<li className='note'>
762-
Your awesome note: {note.content} // highlight-line
763-
<button onClick={toggleImportance}>{label}</button>
764-
</li>
765-
)
766-
}
767-
768-
export default Note
769-
```
770-
771-
the _getByText_ method that the test uses does <i>not</i> find the element
772-
773-
```js
774-
test('renders content', () => {
775-
const note = {
776-
content: 'Does not work anymore :(',
777-
important: true
778-
}
779-
780-
render(<Note note={note} />)
781-
782-
const element = screen.getByText('Does not work anymore :(')
783-
784-
expect(element).toBeDefined()
785-
})
786-
```
787-
788-
The _getByText_ method looks for an element that has the **same text** that it has as a parameter, and nothing more. If we want to look for an element that <i>contains</i> the text, we could use an extra option:
789-
790-
```js
791-
const element = screen.getByText(
792-
'Does not work anymore :(', { exact: false }
793-
)
794-
```
795-
796-
or we could use the _findByText_ method:
797-
798-
```js
799-
const element = await screen.findByText('Does not work anymore :(')
800-
```
801-
802-
It is important to notice that, unlike the other _ByText_ methods, _findByText_ returns a promise!
803-
804-
There are situations where yet another form of the _queryByText_ method is useful. The method returns the element but <i>it does not cause an exception</i> if it is not found.
805-
806-
We could eg. use the method to ensure that something <i>is not rendered</i> to the component:
807-
808-
```js
809-
test('does not render this', () => {
810-
const note = {
811-
content: 'This is a reminder',
812-
important: true
813-
}
814-
815-
render(<Note note={note} />)
816-
817-
const element = screen.queryByText('do not want this thing to be rendered')
818-
expect(element).toBeNull()
819-
})
820-
```
821-
822797
### Test coverage
823798
824799
We can easily find out the [coverage](https://vitest.dev/guide/coverage.html#coverage) of our tests by running them with the command.

0 commit comments

Comments
 (0)