Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save robbiejaeger/1c7839a1832036ec06ad85c9d74c740c to your computer and use it in GitHub Desktop.
Save robbiejaeger/1c7839a1832036ec06ad85c9d74c740c to your computer and use it in GitHub Desktop.

Testing Apps with Router and Network Requests (Asychronous Code)

Router

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 (Asynchronous Code)

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:

  1. Add async to the it callback to tell Jest that there is something asynchronous happening in this test
  2. Use waitForElement to wait for a specific element on the page that I know will be on the page when the network request completes
  3. Use await to wait for that element (in front of waitForElement())

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment