I'm trying to develop BBS App with TypeScript, React, React Router, React Testing Library, but a component with useParams
doesn't pass a test. It looks like work correctly on the browser.
This is my component Thread.tsx
import { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
import RedichanNav from 'RedichanNav';
import 'bootstrap/dist/css/bootstrap.min.css';
import 'Thread.css';
import Stack from 'react-bootstrap/Stack';
import Nav from 'react-bootstrap/Nav';
import Navbar from 'react-bootstrap/Navbar';
import consola from 'consola';
import Container from 'react-bootstrap/Container';
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';
interface Post {
id: number;
poster: string;
text: string;
UTCTimeStamp: Date;
}
const Thread = (): JSX.Element => {
const { id } = useParams();
// id's type is string | undefined.
if (id === undefined) {
throw new TypeError(`Expected 'id' to be defined, but received undefined`);
}
const threadURI = `http://localhost:4000/api/thread/${id}`;
const [thread, setThread] = useState(new Array<Post>());
useEffect(() => {
const fetchThread = async () => {
const response = await fetch(threadURI);
const result = (await response.json()) as Array<Post>;
setThread(result);
};
fetchThread().catch((err) => consola.error(err));
}, [id]);
return (
<div className="Board mx-auto">
<RedichanNav />
<Stack className="mb-5">
{thread.map((post, index) => (
<Container key={post.id} className="bg-light border">
<Row>
<Col>{index}</Col>
</Row>
<Row>
<Col className="post-text">{post.text}</Col>
</Row>
</Container>
))}
{/* br for layout. */}
<br />
</Stack>
<Navbar bg="light" variant="light" fixed="bottom">
<Nav className="ms-auto">
<Nav.Link href="#post">➕</Nav.Link>
</Nav>
</Navbar>
</div>
);
};
export default Thread;
And my index.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import 'index.css';
import Home from 'Home';
import Board from 'Board';
import Thread from 'Thread';
import reportWebVitals from 'reportWebVitals';
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/board/en/news" element={<Board name="enNews" />} />
<Route path="/board/ja/news" element={<Board name="jaNews" />} />
<Route path="/thread/:id" element={<Thread />} />
</Routes>
</BrowserRouter>
);
reportWebVitals();
And my test Thread.test.tsx
import { render, screen } from '@testing-library/react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import Home from './Home';
import Thread from './Thread';
test('renders thread', () => {
render(
<BrowserRouter>
<Routes>
<Route path="/thread/0" element={<Thread />} />
</Routes>
</BrowserRouter>
);
const post= screen.getByText('➕');
expect(post).toBeInTheDocument();
});
And I got
FAIL src/Thread.test.tsx
✕ renders thread (168 ms)
● renders thread
TestingLibraryElementError: Unable to find an element with the text: ➕. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.
Ignored nodes: comments, script, style
<body>
<div />
</body>
13 | );
14 |
> 15 | const post= screen.getByText('➕');
| ^
16 |
17 | expect(post).toBeInTheDocument();
18 | });
at Object.getElementError (node_modules/@testing-library/dom/dist/config.js:40:19)
at node_modules/@testing-library/dom/dist/query-helpers.js:90:38
at node_modules/@testing-library/dom/dist/query-helpers.js:62:17
at getByText (node_modules/@testing-library/dom/dist/query-helpers.js:111:19)
at Object.<anonymous> (src/Thread.test.tsx:15:22)
at TestScheduler.scheduleTests (node_modules/@jest/core/build/TestScheduler.js:333:13)
at runJest (node_modules/@jest/core/build/runJest.js:404:19)
It looks like the component is empty.
The BrowserRouter
sort of depends on a DOM environment, i.e. like the browser, to function correctly. This doesn't work well in unit tests though. For unit testing we'll often use the MemoryRouter
which uses its own memoryHistory
object instead of relying on synchronizing with a browser's history stack.
Import the MemoryRouter
for use in unit tests to provide the routing context. Remember that the default initial entry is "/"
, so if you have other routes you should also pass an initialEntries
prop (e.g. synonymous to a "history stack"), and if necessary the initialIndex
for the entry you want to render with. initialIndex
defaults to the last entry of `initialEntries.
Example:
import { render, screen } from '@testing-library/react';
import { MemoryRouter as Router, Routes, Route } from 'react-router-dom';
import Home from './Home';
import Thread from './Thread';
test('renders thread', () => {
render(
<Router initialEntries={["/thread/0"]}>
<Routes>
<Route path="/thread/:id" element={<Thread />} />
</Routes>
</Router>
);
const post= screen.getByText('➕');
expect(post).toBeInTheDocument();
});