16

I'm trying to test a login component. Specifically that it redirects on a successful login. It works fine when testing it manually. But in my test it never does the redirect and it can therefor not find the "Logout" link:

test('successfully logs the in the user', async () => {
  const fakeUserResponse = {success: true, token: 'fake_user_token'}
  jest.spyOn(window, 'fetch').mockImplementationOnce(() => {
    return Promise.resolve({
      json: () => Promise.resolve(fakeUserResponse),
    })
  })
  const { getByText, getByLabelText, findByTestId } = render(<Router><Login /></Router>)

  fireEvent.change(getByLabelText(/email/i), {target: {value: 'dan@example.com'}})
  fireEvent.change(getByLabelText(/password/i), {target: {value: 'password1234'}})
  fireEvent.click(getByText(/submit/i))

  await waitForElement(() => getByText(/logout/i));
})

I'm redirecting with react-router version 4, like so:

{state.resolved ? <Redirect to="/" /> : null}

Am I going about this the wrong way?

Javad Khodadadi
  • 410
  • 1
  • 4
  • 13
dan-klasson
  • 13,734
  • 14
  • 63
  • 101

4 Answers4

14

You can mock the Redirect component's implementation to show some text including the path instead of redirecting to it:

jest.mock('react-router-dom', () => {
  return {
    Redirect: jest.fn(({ to }) => `Redirected to ${to}`),
  };
});

and expect your component to display the text with the correct path:

expect(screen.getByText('Redirected to /')).toBeInTheDocument();
clemlatz
  • 7,543
  • 4
  • 37
  • 51
  • 1
    This, I found, is the best way to mock and test Redirect with react-router-dom. As a best practice, I also tend to unmock the package after all tests have ran - like ```afterAll(() => { jest.unmock('react-router-dom'); });``` – dhruvpatel Aug 04 '23 at 18:30
9

In my case, this gets the job done:

it('should do stuff and redirect to /some/route', async () => {
  // given
  const history = createMemoryHistory();

  // when
  render(<Router history={history}><MyComponent /></Router>);

  // do stuff which leads to redirection

  // then
  await waitFor(() => {
    expect(history.location.pathname).toBe('/some/route');
  });
});
Matthieu Dsprz
  • 385
  • 6
  • 10
  • When using this with React router 6.4 I get the error `ReferenceError: Request is not defined` – Liam Oct 24 '22 at 08:22
8

Personally I mock the history.replace function that is used by the Redirect component.

const history = createBrowserHistory();
history.replace = jest.fn();

render(
  <Router history={history} >
    <Component />
  </Router>
);

// trigger redirect

expect(history.replace).toHaveBeenCalledWith(expect.objectContaining({
  "pathname": "/SamplePath",
  "search": "?SampleSearch",
  "state": { "Sample": "State" }
}));

This allows you to check for more than just the correct path. Please ensure you are using Router and not BrowserRouter in your test. The latter does not accept a history prop.

4FCG
  • 81
  • 1
  • 2
4

So I ended up doing this:

const { getByText, getByLabelText, } = render(
  <Router>
    <Login/>
    <Switch>
      <Route path="/">
        <div>logged out</div>
      </Route>
    </Switch>
  </Router>
)

fireEvent.change(getByLabelText(/email/i), {target: {value: 'dan@example.com'}})
fireEvent.change(getByLabelText(/password/i), {target: {value: 'password1234'}})
fireEvent.click(getByText(/submit/i))

await waitForElement(() => getByText(/logged out/i))
dan-klasson
  • 13,734
  • 14
  • 63
  • 101