Reputation: 792
How do I unit test the component in react router v4? I am unsuccessfully trying to unit test a simple component with a redirect using jest and enzyme.
My component:
const AppContainer = ({ location }) =>
(isUserAuthenticated()
? <AppWithData />
: <Redirect
to={{
pathname: "/login",
state: { from: location }
}}
/>);
My attempt to test it:
function setup() {
const enzymeWrapper = mount(
<MemoryRouter initialEntries={["/"]}>
<AppContainer />
</MemoryRouter>
);
return {
enzymeWrapper
};
}
jest.mock("lib/authAPI", () => ({
isUserAuthenticated: jest.fn(() => false)
}));
describe("AppContainer component", () => {
it("renders redirect", () => {
const { enzymeWrapper } = setup();
expect(enzymeWrapper.find("<Redirect></Redirect>")).toBe(true);
});
});
Upvotes: 25
Views: 25360
Reputation: 989
Neither of these answers worked for me and took a fair bit of digging so I thought I'd chip in my experience here.
export const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={(props) => (
auth.isAuthenticated
? <Component {...props} />
: <Redirect to={{
pathname: '/',
state: { from: props.location }
}} />
)} />
)
This test worked for me with no problems whatsoever, it rendered the PrivateComponent
when auth.isAuthenticated
evaluated to true.
it('renders the component when the user is authorised', () => {
auth.login()
expect(auth.isAuthenticated).toBe(true)
const privateRoute = mount(
<MemoryRouter initialEntries={['/privateComponent']}>
<PrivateRoute path='/privateComponent' component={PrivateComponent} />
</MemoryRouter>
)
expect(privateRoute.find('PrivateComponent').length).toEqual(1)
})
This was the test that gave me a lot of issues. At first I was checking for the Redirect
component.
I tried to just do something like
expect(privateRoute.find('Redirect').length).toEqual(1)
But that just wouldn't work, no matter what I did, it just couldn't find the Redirect
component. In the end, I ended up checking the history but couldn't find any reliable documentation online and ended up looking at the React Router codebase.
In MemoryRouter.js (line 30) I saw that it rendered a Router
component. I noticed that it was also passing it's history
as a prop to Router
so I figured I would be able to grab it from there.
I ended up grabbing the history prop from Router
using privateRoute.find('Router').prop('history')
which then finally gave me evidence that a redirect had actually happened, to the correct location, no less.
it('renders a redirect when the user is not authorised', () => {
auth.logout()
expect(auth.isAuthenticated).toBe(false)
const privateRoute = mount(
<MemoryRouter initialEntries={['/privateComponent']}>
<PrivateRoute path='/privateComponent' component={PrivateComponent} />
</MemoryRouter>
)
expect(privateRoute.find('PrivateComponent').length).toEqual(0)
expect(
privateRoute.find('Router').prop('history').location.pathname
).toEqual('/')
})
With this test, you're testing the actual functionality of the PrivateRoute
component and ensuring that it goes where it's saying it's going.
The documentation leaves a lot to be desired. For example, it took a fair bit of digging for me to find out about initialEntries
as a prop for MemoryRouter
, you need this so it actually hits the route and executes the conditional, I spent too long trying to cover both branches only to realise this was what was needed.
Hope this helps someone.
Upvotes: 18
Reputation: 3557
Here's my minimal example of testing that the actual URL changes instead of just that a Redirect
component exists on the page:
RedirectApp.js
:
import React from "react";
import { Route, Switch, Redirect } from "react-router-dom";
const RedirectApp = props => {
return (
<Switch>
<Redirect from="/all-courses" to="/courses" />
</Switch>
);
};
export default RedirectApp;
RedirectApp.test.js
:
import React from "react";
import { MemoryRouter, Route } from "react-router-dom";
import { mount } from "enzyme";
import RedirectApp from "./RedirectApp";
it("redirects /all-courses to /courses", () => {
const wrapper = mount(
<MemoryRouter initialEntries={[`/all-courses`]}>
<Route component={RedirectApp} />
</MemoryRouter>
);
expect(wrapper.find(RedirectApp).props().location.pathname).toBe("/courses");
});
By wrapping RedirectApp
in a Route
, MemoryRouter
injects the react-router
props (match
, location
, and history
) in RedirectApp
.
enzyme
lets you grab these props()
, and the location
prop includes the pathname
after redirect, so the redirected location can be matched.
This method is a little hacky, but has the advantage of testing that a redirect is going to the correct place and not just that a Redirect
exists.
Alternatively, you can export default withRouter(RedirectApp)
in RedirectApp.js
to automatically get the react-router
props injected.
Upvotes: 5
Reputation: 792
Answering my own question. Basically I'm making a shallow render of my component and verifying that if authenticated is rendering the redirect component otherwise the App one. Here the code:
function setup() {
const enzymeWrapper = shallow(<AuthenticatedApp />);
return {
enzymeWrapper
};
}
describe("AuthenticatedApp component", () => {
it("renders Redirect when user NOT autheticated", () => {
authApi.isUserAuthenticated = jest.fn(() => false);
const { enzymeWrapper } = setup();
expect(enzymeWrapper.find(Redirect)).toHaveLength(1);
});
it("renders AppWithData when user autheticated", () => {
authApi.isUserAuthenticated = jest.fn(() => true);
const { enzymeWrapper } = setup();
expect(enzymeWrapper.find(AppWithData)).toHaveLength(1);
});
});
Upvotes: 23