10

I have a component that I am using in React Router v6 for managing private routes, that does some checks on an auth token, and will either render the Outlet component or will redirect to a login page.

I have -

import { Outlet } from 'react-router-dom';

export const CheckAuth = (props) => {
  const valid = ...;
  if (!valid) {
    window.location.replace(loginUrl);
    return null;
  }

  return <Outlet />;
};

and using it like -

<Route element={<CheckAuth token={authToken} />}>
   // ... private routes ...
</Route>

I can mock out window.location.replace with Jest

delete window.location;
window.location = { replace: jest.fn() };
...
render(<CheckAuth token={token} />)
expect(window.location.replace).toHaveBeenCalledWith(loginUrl);

but how can I test the Outlet component using Testing Library?

2
  • 1
    Unrelated, why are you using window.location? You probably want to render a redirect, i.e. <Navigate to={loginUrl} replace /> instead of mutating the location. Commented Jan 10, 2022 at 17:15
  • It's an external URL which I believe react-router doesn't handle? Commented Jan 10, 2022 at 17:27

2 Answers 2

11

If it helps anyone, I ended up just wrapping the components in the test with a react router components, and passed a dummy component as a child to Route and asserted that some fake text in that component was or was not rendered

Outside the test block -

const FakeComponent = () => <div>fake text</div>;

and for a failure scenario, where the outlet should not render -

    render(
      <MemoryRouter initialEntries={['/']}>
        <Routes>
          <Route element={<CheckAuth />}>
            <Route path="/" element={<FakeComponent />} />
          </Route>
        </Routes>
      </MemoryRouter>
    );

    await waitFor(() => expect(screen.queryByText('fake text')).not.toBeInTheDocument());
    await waitFor(() => expect(window.location.replace).toHaveBeenCalledWith(loginUrl));

and for a success scenario, assert that the text is present -

render(
      <MemoryRouter initialEntries={['/']}>
        <Routes>
          <Route element={<CheckAuth token={correctToken}/>}>
            <Route path="/" element={<FakeComponent />} />
          </Route>
        </Routes>
      </MemoryRouter>
    );

    expect(screen.getByText('fake text')).toBeInTheDocument();
Sign up to request clarification or add additional context in comments.

Comments

0

For anyone viewing this in 2024, I tried the accepted answer and while it does work, a colleague mentioned that it does not actually test the routing logic in App.jsx or App.tsx (which is true).

So, here's how you should actually go about it:

- You need to wait for the component to actually render before checking whether it did

For example, if you have a private route for /home that passes the auth token checks and renders the component below:

const HomePage = () => {
  //...some logic 
  
  return <div>Home page</div>

}

And your App.jsx looks like this:

import { Outlet } from 'react-router-dom';
// other imports

export const App = (props) => {
  //... app logic

 return (
   <Routes>
    <Route path='/' element={<Outlet />}>
       <Route element={<CheckAuth token={authToken} />}>
          // ... all private routes including '/home' ...
       </Route>
    </Route>
   </Routes>
 )
}

index.js:

import App from 'App'

const root = createRoot(document.getElementById('root'))
  root.render(
    <BrowserRouter>
      <App />
    </BrowserRouter>
  )

Then, your tests should look like this:

describe("App page", async () => {

   ...
   delete window.location;
   window.location = { replace: jest.fn() };
   ...


   test('should test for private route with invalid authToken', () => {

      // example private route with no valid auth: '/not-valid'
      render(
        <MemoryRouter initialEntries={['/not-valid']}>
          <App />
        </MemoryRouter>
      )

      expect(window.location.replace).toHaveBeenCalledWith(loginUrl)
   })

   test('should test for private route with valid authToken', () => {
      render(
        <MemoryRouter initialEntries={['/home']}>
          <App />
        </MemoryRouter>
      )

      expect(await screen.findByText('Home page')).toBeInTheDocument()
   })
})

The key point here is to ensure you wait for the rendering to have happened before checking for whether it has successfully rendered the component for /home

And you do this by checking with an await i.e expect(await screen.findBy........

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.