I have my react-router component such as :
<Switch>
<Route
path="/abc"
render={() => <ComponentTemplateABC component={containerABC} />}
/>
<Route
path="/def"
render={() => <ComponentTemplateDEF component={containerDEF} />}
/>
...
...
</Switch>
I wish to test the routing to ensure the respective component is rendered for each route. However, I do not wish to use mount for testing the routing, only wish to use shallow rendering.
Below is what my test looks like currently:
test('abc path should route to containerABC component', () => {
const wrapper = shallow(
<Provider store={store}>
<MemoryRouter initialEntries={['/abc']}>
<Switch>
<AppRouter />
</Switch>
</MemoryRouter>
</Provider>,
);
jestExpect(wrapper.find(containerABC)).toHaveLength(1);
});
This test does not work with shallow, because shallow won't render the full child hierarchy. So I tried an alternate approach:
test('abc path should render correct routes and route to containerABC component', () => {
const wrapper = shallow(<AppRouter />);
const pathMap = wrapper.find(Route).reduce((pathMap, route) => {
const routeProps = route.props();
pathMap[routeProps.path] = routeProps.component;
return pathMap;
}, {});
jestExpect(pathMap['/abc']).toBe(containerABC);
});
This test does not work for me, because I am using render in my routing code instead of Component directly as in below:
<Route path="..." **render**={() => <Component.. component={container..} />}
Hence, I am unable to test my routes. How do I test my routes using shallow rendering or as above or basically, any other approach that does not use mount?
Any help would be much appreciated.
Thank you in advance.
So far I may suggest you different ways to test that:
Mocking ComponentABC + mount()
import containerABC from '../../containerABC.js';
jest.mock('../../containerABC.js', () => <span id="containerABC" />);
...
const wrapper = mount(
<Provider store={store}>
<MemoryRouter initialEntries={['/abc']}>
<Switch>
<AppRouter />
</Switch>
</MemoryRouter>
</Provider>,
);
jestExpect(wrapper.find(containerABC)).toHaveLength(1);
shallow() + dive() + renderProp():
const wrapper = shallow(
<Provider store={store}>
<MemoryRouter initialEntries={['/abc']}>
<Switch>
<AppRouter />
</Switch>
</MemoryRouter>
</Provider>,
);
jestExpect(wrapper.find(AppRouter)
.dive()
.find(Route)
.filter({path: '/abc'})
.renderProp('render', { history: mockedHistory})
.find(ContainerABC)
).toHaveLength(1);
Related
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?
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>
);
expect(screen.queryByText('fake text')).not.toBeInTheDocument();
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.queryByText('fake text')).toBeInTheDocument();
I'm new to React Testing library and facing some challenges in writing the test case for render function in React router.
I'm unsure to get the code coverage for the render function of Route. Can some one guide me in that.
<Route path="/employee" render={() => <div><h4>Employee Page</h4></div>} />
App.test.js
it('should render Employee Page', () => {
render(
<MemoryRouter initialEntries={['/employee']}>
<App/>
</MemoryRouter>
);
expect(screen.getByText('Employee Page')).toBeInTheDocument();
});
App.js
<Router>
<div>
<ul>
<li><NavLink to="/home">Home</NavLink></li>
<li><NavLink to="/article">Article</NavLink></li>
<li><NavLink to="/employee">Employee</NavLink></li>
</ul>
<Switch>
<Route path="/home" render={() => <div><h4>Home
Page</h4></div>} />
<Route path="/article" render={() => <div><h4>Article
Page</h4></div>} />
<Route path="/employee" render={() => <div><h4>Employee
Page</h4></div>} />
</Switch>
</div>
</Router>
Finally I successfully completed the unit testing of React Router and got the code coverage of render function of Route.
App.test.js
import {createMemoryHistory} from 'history';
it('should render Employee', () => {
const history = createMemoryHistory(["/", "/employee"])
history.push('/employee');
const component = <Router history={history}>
<NavLink to="/employee">Employee</NavLink>
<App/>
</Router>
render(component);
});
I know this issue has been discussed before. But, somehow I cannot get it work in my application.
Normally, the navigation works fine between components. However, history.push only changes the url but does not update the content. For example, in the Login page, I want to navigate user to Home page if already logged in. But, the code only updates the url. Any ideas?
const Login = () => {
useEffect(() => {
if (authenticationService.currentUserValue != null) {
history.push('/home');
}
}, [])
//other code
}
In index.js, I have the following
<BrowserRouter basename={baseUrl} history={history}>
<Provider store={store}>
<App />
</Provider>
</BrowserRouter >,
In app.js, I have:
<Layout>
<Switch>
<PrivateRoute exact path="/home" component={withRouter(Home)} />
<Route exact path='/home' component={withRouter(Home)} />
<Route exact path='/login' component={Login} />
<Route exact path='/register' component={withRouter(Register)} />
</Switch>
</Layout>
The issue in your case is that you are using a custom history with BrowserRouter which isn't correct. BrowserRouter uses its own history and you must use that to change pages
const Login = ({history}) => {
useEffect(() => {
if (authenticationService.currentUserValue != null) {
history.push('/home');
}
}, [])
//other code
}
If you have used custom history for a reason, then you need to use Router with a custom history prop
<Router basename={baseUrl} history={history}>
<Provider store={store}>
<App />
</Provider>
</Router >
While I'm ultimately trying to write an Enzyme test for the flow in this react router example: https://reacttraining.com/react-router/web/example/auth-workflow
import React from 'react';
import ReactDOM from 'react-dom';
import Enzyme, { shallow, mount } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import { MemoryRouter, Route, Switch, Link } from 'react-router-dom';
const Home = () => <div>Home</div>;
const MockComp = () => (
<div className="protected">
<nav>hi</nav>
Protected
</div>
);
const MockDenied = () => <div className="denied">Denied</div>;
test('Renders visited protected component if authorized', () => {
const wrapper = mount(
<MemoryRouter initialEntries={['/']}>
<div>
<Link to="/foo" />
<Switch>
<Route path="/" component={Home} />
<Route path="/401" component={MockDenied} />
<ProtectedRouteBasic
path="/foo"
auth={{ hasAuth: true }}
component={MockComp}
/>
</Switch>
</div>
</MemoryRouter>
);
wrapper.find('a').simulate('click', { button: 0 });
expect(wrapper.find('.protected').length).toEqual(1);
expect(wrapper.find('.denied').length).toEqual(0);
});
I've found a number of issues and have tried to peel away the complexity and then slowly reintroduce the elements that I've removed.
So I've landed on this test as what I will need to get working to proceed:
test('Clicking link will render component associated with path', () => {
const wrapper = mount(
<MemoryRouter>
<div>
<Link to="/foo" />
<Switch>
<Route path="/" component={Home} />
<Route path="/foo" component={MockComp} />
</Switch>
</div>
</MemoryRouter>
);
wrapper.find('a').simulate('click', { button: 0 });
expect(wrapper.find('.protected')).toHaveLength(1);
});
However, this test isn't working as expected as I expect the test to pass in its current state. I've read this thread to update my simulate call to include the {button: 0} as well as this thread about wrapping the entire router in a functional component, however, that option's not available to me as far as I know, since the framework I'm working with doesn't seem to allow for it. Additionally, I believe that that piece is immaterial to the issue I'm having. That said, any help would be much appreciated.
From the Switch docs:
Renders the first child <Route> or <Redirect> that matches the location.
In this case <Route path="/" component={Home} /> matches when the path is both / and /foo so Home is always rendered.
You can fix this by using either exact so it only matches if the path is exactly /, or moving it to the end of the Route list so other routes match first:
test('Clicking link will render component associated with path', () => {
const wrapper = mount(
<MemoryRouter>
<div>
<Link to="/foo" />
<Switch>
<Route path="/foo" component={MockComp} />
<Route path="/" component={Home} />
</Switch>
</div>
</MemoryRouter>
);
wrapper.find('a').simulate('click', { button: 0 });
expect(wrapper.find('.protected')).toHaveLength(1); // SUCCESS
});
For some reason, I can't reach router in my tests.
My render in main container:
render() {
return (
<div className="AppEntry-scope">
<BrowserRouter>
<Switch>
<Route exact path="/" component={Dashboard} />
// ...Other routes
<Route component={FourOhFourNothinFound} />
</Switch>
</BrowserRouter>
</div>
);
}
My test:
describe('<Dashboard />', () => {
it('Should see dashboard', () => {
const wrapper = mount(
<MemoryRouter initialEntries={['/']}>
<AppEntry store={store} />
</MemoryRouter>,
);
expect(wrapper.find(Dashboard)).toBePresent();
});
});
Also, I've tried:
expect(wrapper.find(Dashboard).length).toBe(1);
And my test doesn't see Dashboard component.
In my app this route is works, but I want to set up test for routes.
BTW, when I tried to test my notfound component FourOhFourNothinFound - it works and seems my tests always see notfound component.
What is wrong and how I can set up my react-router tests correctly?