Testing redirect after submit with React Testing Library
Asked Answered
S

4

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: '[email protected]'}})
  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?

Sewell answered 13/10, 2019 at 11:4 Comment(0)
L
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();
Luxor answered 1/9, 2020 at 13:8 Comment(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'); });Burner
S
10

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');
  });
});
Sheff answered 21/2, 2022 at 15:33 Comment(2)
When using this with React router 6.4 I get the error ReferenceError: Request is not definedExternalize
@Externalize I'm seeing a similar error, did you ever find an answer?Meistersinger
H
9

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.

Halfcock answered 5/9, 2021 at 14:53 Comment(0)
S
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: '[email protected]'}})
fireEvent.change(getByLabelText(/password/i), {target: {value: 'password1234'}})
fireEvent.click(getByText(/submit/i))

await waitForElement(() => getByText(/logged out/i))
Sewell answered 13/10, 2019 at 15:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.