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?
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 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);
I'm trying to apply code splitting to my React app. The below code is my routes with code splitting applied by Loadable from react-loadable. With the implementation, every time I access each page, the code chunk from webpack is properly loaded. However, when I access my webpage initially, it brings about screen flicker initially. After that, if I move to another page, no more screen flicker. It only happens when accessing my website initially.
Does anyone know why it's happening? Is there something wrong on my configuration?
By the way, I used server-side rendering with Node.js in my React app. Does it have something to do with screen flicker by code splitting?
routes.jsx
import React from "react";
import Loadable from "react-loadable";
import { Route, Switch } from "react-router-dom";
import NotFound from "routes/not-found";
const Landing = Loadable({
loader: () => import(/* webpackChunkName: "landing" */ "routes/landing"),
loading: () => null,
modules: ["landing"]
});
const About = Loadable({
loader: () => import(/* webpackChunkName: "about" */ "routes/about"),
loading: () => null,
modules: ["about"]
});
export default props => {
return (
<Switch>
<Route
exact
path="/"
render={routeProps => <Landing {...routeProps} options={options} />}
/>
{/* <Route exact path="/" component={Homepage} /> */}
<Route
exact
path="/about"
render={routeProps => <About {...routeProps} options={options} />}
/>
<Route component={NotFound} />
</Switch>
);
};
src/index.js
const { store, history } = createStore();
const generateClassName = createGenerateClassName();
const Application = (
<JssProvider generateClassName={generateClassName}>
<MuiThemeProvider theme={theme}>
<Provider store={store}>
<ConnectedRouter history={history}>
<Frontload noServerRender={true}>
<App />
</Frontload>
</ConnectedRouter>
</Provider>
</MuiThemeProvider>
</JssProvider>
);
const root = document.querySelector("#root");
if (root.hasChildNodes() === true) {
// If it's an SSR, we use hydrate to get fast page loads by just
// attaching event listeners after the initial render
Loadable.preloadReady().then(() => {
hydrate(Application, root, () => {
document.getElementById("jss-styles").remove();
});
});
} else {
// If we're not running on the server, just render like normal
render(Application, root);
}
the flickering is happening because the router don't have the source of your component when routing and it renders null as you defined in loading func provided to Loadable HOC
to prevent flickering you can render a Loading component that should look something like this: https://github.com/osamu38/react-loadable-router-example/blob/master/src/components/Loading.js
or you can preload all the pages onComponentDidMount like this (you should convert your component to class component for it):
componentDidMount = () => {
About.preload()
Landing.preload()
}
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
});