With React Router, any component that uses a part of the react-router-dom
library (like Route
, Link
, Navlink
, etc.) needs to have access to a Router
component. Since the BrowserRouter
(which is a type of Router
) lives in the index.js
file something like this:
// index.js
import { BrowserRouter } from 'react-router-dom';
const router = (
<BrowserRouter>
<App />
</BrowserRouter>
);
ReactDOM.render(router, document.getElementById('root'));
Each component, when tested on its own, will not have the BrowserRouter
wrapped around it. You will see an error in your tests along the lines of your component needing access to a Router
.
To fix this in your tests, wrap the component that you are rendering in a <Router>
component with a new history, like this:
// some component test
// import the plain Router and "history" tooling into the test file
import { Router } from 'react-router-dom';
import { createMemoryHistory } from 'history';
// within an "it" block
const history = createMemoryHistory(); // this creates a fresh new routing history
const { someQueriesHere } = render(
<Router history={history}>
<App />
</Router>
);
Think of creating a new history with createMemoryHistory
is like refreshing your browser starting at the /
route. This refresh will have for every test now if you continue to call createMemoryHistory
in each it
block.
Network requests take time to complete, and our tests don't know how to wait until the network requests are complete. So they'll continue on running the remainder of a test while your app is still trying to fetch data. This might lead to some confusing issues in your test.
To get around this and make our tests wait for certain things, we need to use the Async Utilities that come with DOM Testing Library (which is given to you by React Testing Library).
To describe the situation, let's say that you have App
requesting data from the server, and when that data is available, it renders on the page. If our test looks something like:
// a test in App.test.js for example
it('should be able to delete a card (integration test - without network requests)', () => {
const { queryAllByText } = render(<App />);
fireEvent.click(queryAllByText('Delete')[0]);
// the test fails because the Delete button for each idea card is not on the page quite yet
});
Then the fireEvent
will try to click the Delete button before it even shows on the page. Here is how we can use asyc/await
and waitForElement() to wait for the Delete button (idea cards) to appear on the page before trying to click it:
it('should be able to delete a card (integration test - with network request)', async () => {
const { findByText, queryAllByText, getByTestId } = render(<App />);
await waitForElement(() => findByText('Sweaters for pugs'));
fireEvent.click(queryAllByText('Delete')[0]);
//...continue with test
});
Three things were done here:
- Add
async
to theit
callback to tell Jest that there is something asynchronous happening in this test - Use
waitForElement
to wait for a specific element on the page that I know will be on the page when the network request completes - Use
await
to wait for that element (in front ofwaitForElement()
)
Now the test will wait for that element to appear on the page before moving on to the next part of the test.
We'll go more in depth about how to fake network requests when we talk specifically about testing asychronous JavaScript with Jest and React Testing Library.