Testing Lists Items With React Testing Library
2 mins |
November 22, 2020Most of the applications usually have some kind of lists. They are a bit different to test as they contain dynamic values. Let’s see how we can test them using React Testing Library.
We can explore that by an example by a list of Fruits. Here we lists 5 static fruits, but this could remain almost same even if we have dynamically updated lists.
1const fruits = ["Bananas", "Apples", "Strawberries", "Grapes", "Oranges"]23function FruitList() {4 return (5 <section>6 <h1 id="fruits-heading">Fruits</h1>7 <ul aria-labelledby="fruits-heading">8 {fruits.map(fruit => (9 <li key={fruit}>{fruit}</li>10 ))}11 </ul>12 </section>13 )14}
Key things to remember📝:
- use
<ul>
,<ol>
and<li>
to semantically list items. If you can’t use them, at least add appropriate aria roles. - add aria label (
aria-label
andaria-labelledby
) to<ul>
or<ol>
to semantically label what the list is about.
Let us see how we can test this list of fruits. Most often we don’t want to test exact order in which the elements were rendered or sometimes what exactly the items were. In these cases all we need is to see how many elements were rendered.
If you want to just test whether X items are rendered
1import React from "react"2import { render, screen, within } from "@testing-library/react"34it("should render list of 5 fruits", () => {5 render(<FruitList />)6 const list = screen.getByRole("list", {7 name: /fruits/i,8 })9 const { getAllByRole } = within(list)10 const items = getAllByRole("listitem")11 expect(items.length).toBe(5)12})
We are able to query the the list by
screen.getByRole("list", { name: /fruits/i })
because we have labelled the
<ul>
with aria-label="fruits"
.
Once we get the list (<ul>
), we need to get all the list items rendered under
the list. We can achieve that by using
within
(RTL provides this method which can be used to query within an element). Here we
use it to query all the listitem
under the <ul>
. If we are unable to use
<li>
or add listitem
role to the list, you can add data-testid
. I would
strongly discourage 👎 as it might not be accessible.
This might not be enough in some cases. We might have lists where you are listing the items in chronological order or we have a feature where a list is sorted on some conditions. In these cases, we might want to test the order in which the elements were rendered. We can achieve that by taking a snapshot of the list.
If you want to test the list as a snapshot
1import React from "react"2import { render, screen } from "@testing-library/react"34it("should render list of fruits in a specific order by dom snapshot", () => {5 render(<FruitList />)6 const list = screen.getByRole("list", {7 name: /fruits/i,8 })9 expect(list).toMatchInlineSnapshot(`10 <ul11 aria-label="Fruits"12 >13 <li>14 Bananas15 </li>16 <li>17 Apples18 </li>19 <li>20 Strawberries21 </li>22 <li>23 Grapes24 </li>25 <li>26 Oranges27 </li>28 </ul>29 `)30})
Here we are testing the snapshot of the DOM, which might not be easy to read when the list item is huge. It might have a lot more elements than what we have right now. In these cases, we would be better off with testing the name or label of each element instead of taking the snapshot of the entire list.
Here we can just pick the textContent
from the DOM element. This should be
fine in most of the cases.
If you want to test important bits of the list
1import React from "react"2import { render, screen, within } from "@testing-library/react"34it("should render list of fruits in a specific order", async () => {5 render(<FruitList />)6 const list = screen.getByRole("list", {7 name: /fruits/i,8 })9 const { getAllByRole } = within(list)10 const items = getAllByRole("listitem")11 const fruitNames = items.map(item => item.textContent)12 expect(fruitNames).toMatchInlineSnapshot(`13 Array [14 "Bananas",15 "Apples",16 "Strawberries",17 "Grapes",18 "Oranges",19 ]20 `)21})
If for some reason, you don’t want to use toMatchInlineSnapshot
, then you
could also use use toEqual
.
1expect(fruitNames).toEqual([2 "Bananas ",3 "Apples ",4 "Strawberries ",5 "Grapes ",6 "Oranges ",7])
The benefit we get out of creating the list like fruitNames
is that we could
render some non-essential elements in our list and expect our test to pass.
for eg. let’s say we made the list to show some image
1{fruits.map(fruit => (2 <li key={fruit}>3 {fruit}4+ <img src={imageSource} alt={fruit} />5 </li>6))}
Our tests snapshot will break 💔 because now the list item contains image.
1Bananas2+ <img3+ alt="Bananas"4+ src="https://example.com/images/bananas"5+ />
But the tests that test the textContent
will not break 😱 as we are not adding
any more text content. Depending on our use case, we can choose either approach.
I would recommend taking text content (or any other relevant information) from
the item instead of the complete list. We can add some unit tests to cover what
exactly is rendered in each list item.
TL;DR
When testing list items, if you don’t care about the order in which the elements are rendered. We can test the length of the items rendered. And unit test how the list is rendered.
If we care about the ordering, then we can test the snapshot of the list. If the list has a lot of elements, we can pick the most important content from the list item and test against that. And again, we can test how the list item is rendered by unit tests.
Now we know how to test those awesome 🕺 lists you have.
Got a Question? Bala might have the answer. Get them answered on #AskBala
Subscribe Now! Letters from Bala
Subscribe to my newsletter to receive letters about some interesting patterns and views in programming, frontend, Javascript, React, testing and many more. Be the first one to know when I publish a blog.
No spam, just some good stuff! Unsubscribe at any time