How do I test a component which needs the parameter from react-router to work?
Carousel.js component
import { useParams } from "react-router-dom"; import { useState } from "react"; import './Carousel.css'; const Carousel = ({haustierArray}) => { const [active, setActive] = useState(0); const {id} = useParams(); // const id = 1; const { images } = haustierArray.find(haustier => haustier.id === +id); const handleIndexClick = (event) => { setActive(+event.target.dataset.index) } return ( <div className="carousel"> <img src={images[active]} alt="animal" data-testid="hero" /> <div className="carousel-small"> {images.map((photo, index) => ( <img data-testid={`thumbnail${index}`} data-index={index} src={photo} alt="animal thumbnail" key={photo} className={index === active ? "active" : ""} onClick={handleIndexClick} /> ))} </div> </div> ); }; export default Carousel;
Carousel.test.js
import { render, waitFor } from "@testing-library/react"; import Carousel from "./Carousel.js"; it("lets users click on thumbnails to make them the hero image", async () => { const haustierArray = [ { name: "Aristoteles", animal: "Meerschweinchen", breed: "Glatthaar", id: 1, images: ["0.jpg", "1.jpg", "2.jpg", "3.jpg"], }, ]; const carousel = render(<Carousel haustierArray={haustierArray} />); const hero = await carousel.findByTestId("hero"); expect(hero.src).toContain(haustierArray[0].images[0]); for (let i = 0; i < haustierArray[0].images.length; i++) { const image = haustierArray[0].images[i]; const thumb = await carousel.findByTestId(`thumbnail${i}`); thumb.click(); await waitFor(() => { expect(hero.src).toContain(image); expect(thumb.classList).toContain("active"); }); } });
The test works if I comment the line with useParams out and just declare const id = 1;.
Then I’ve tried to use MemoryRouter
.
Carousel.test.js v2
import { render, waitFor } from "@testing-library/react"; import Carousel from "./Carousel.js"; import { MemoryRouter, Routes, Route } from "react-router-dom"; it("lets users click on thumbnails to make them the hero image", async () => { const haustierArray = [ { name: "Aristoteles", animal: "Meerschweinchen", breed: "Glatthaar", id: 1, images: ["0.jpg", "1.jpg", "2.jpg", "3.jpg"], }, { name: "Leo", animal: "Katze", breed: "British", id: 2, images: ["0.png", "1.png"], }, { name: "Lady", animal: "Hund", breed: "Dackel", id: 3, images: ["0.gif", "1.gif", "2.gif"], }, ]; for (let j = 0; j < haustierArray.length; j++) { const carousel = render( <MemoryRouter initialEntries={[`/pictures/${j + 1}`]}> <Routes> <Route path="/pictures/:id" element={<Carousel haustierArray={haustierArray} />} /> </Routes> </MemoryRouter> ); const hero = await carousel.findByTestId("hero"); expect(hero.src).toContain(haustierArray[j].images[0]); for (let i = 0; i < haustierArray[j].images.length; i++) { const image = haustierArray[j].images[i]; const thumb = await carousel.findByTestId(`thumbnail${i}`); thumb.click(); await waitFor(() => { expect(hero.src).toContain(image); expect(thumb.classList).toContain("active"); }); } } });
And the test failed with this output
Found multiple elements by: [data-testid="hero"] Here are the matching elements: Ignored nodes: comments, script, style <img alt="animal" data-testid="hero" src="3.jpg" /> Ignored nodes: comments, script, style <img alt="animal" data-testid="hero" src="0.png" /> (If this is intentional, then use the `*AllBy*` variant of the query (like `queryAllByText`, `getAllByText`, or `findAllByText`)). Ignored nodes: comments, script, style <body> <div> <div class="carousel" > <img alt="animal" data-testid="hero" src="3.jpg" /> <div class="carousel-small" > <img alt="animal thumbnail" class="" data-index="0" data-testid="thumbnail0" src="0.jpg" /> <img alt="animal thumbnail" class="" data-index="1" data-testid="thumbnail1" src="1.jpg" /> <img alt="animal thumbnail" class="" data-index="2" data-testid="thumbnail2" src="2.jpg" /> <img alt="animal thumbnail" class="active" data-index="3" data-testid="thumbnail3" src="3.jpg" /> </div> </div> </div> <div> <div class="carousel" > <img alt="animal" data-testid="hero" src="0.png" /> <div class="carousel-small" > <img alt="animal thumbnail" class="active" data-index="0" data-testid="thumbnail0" src="0.png" /> <img alt="animal thumbnail" class="" data-index="1" data-testid="thumbnail1" src="1.png" /> </div> </div> </div> </body> 40 | ); 41 | > 42 | const hero = await carousel.findByTestId("hero"); | ^ 43 | expect(hero.src).toContain(haustierArray[j].images[0]); 44 | 45 | for (let i = 0; i < haustierArray[j].images.length; i++) { at waitForWrapper (node_modules/@testing-library/dom/dist/wait-for.js:187:27) at findByTestId (node_modules/@testing-library/dom/dist/query-helpers.js:101:33) at Object.<anonymous> (src/components/Carousel.test.js:42:33)
It seems that it worked for one inner loop cycle, but when the outer loop runs the second iteration, 3.jpg
from last iteration is somehow still there. How do I fix it?
Advertisement
Answer
The @testing-library/react
render by default creates a div
and append that div
to the document.body
. Since you’re rendering multiple times in one test, then it would only be apparent that the entire JSX render would be appended multiple times. If you don’t want this to happen, I suggest you perform a cleanup after each rendered loop.
import { render, waitFor, cleanup } from "@testing-library/react"; import Carousel from "./Carousel.js"; import { MemoryRouter, Routes, Route } from "react-router-dom"; it("lets users click on thumbnails to make them the hero image", async () => { const haustierArray = [ { name: "Aristoteles", animal: "Meerschweinchen", breed: "Glatthaar", id: 1, images: ["0.jpg", "1.jpg", "2.jpg", "3.jpg"], }, { name: "Leo", animal: "Katze", breed: "British", id: 2, images: ["0.png", "1.png"], }, { name: "Lady", animal: "Hund", breed: "Dackel", id: 3, images: ["0.gif", "1.gif", "2.gif"], }, ]; for (let j = 0; j < haustierArray.length; j++) { const carousel = render( <MemoryRouter initialEntries={[`/pictures/${j + 1}`]}> <Routes> <Route path="/pictures/:id" element={<Carousel haustierArray={haustierArray} />} /> </Routes> </MemoryRouter> ); const hero = await carousel.findByTestId("hero"); expect(hero.src).toContain(haustierArray[j].images[0]); for (let i = 0; i < haustierArray[j].images.length; i++) { const image = haustierArray[j].images[i]; const thumb = await carousel.findByTestId(`thumbnail${i}`); thumb.click(); await waitFor(() => { expect(hero.src).toContain(image); expect(thumb.classList).toContain("active"); }); } // unmounts the react trees that were mounted with `render`. cleanup(); } });