How to test a React component with RouteComponentProps?
To answer your last question, the recommended approach is to use <MemoryRouter>< *your component here* ></MemoryRouter>
in your tests. Typescript does not pick up that this component will pass the required props to your component, as such I assume it not to be a type safe approach.
This is for React Router v4 and doesn't apply to previous versions.
For a typesafe method to test components that are wrapped with the HOC withRouter
you can build the location, history and match from the react-router
and history
packages.
This example uses enzyme and snapshot testing but could just as easily be any other test.
This avoided me needing to use <MemoryRouter>
as a wrapper that typescript did not like anyhow.
// Other imports hereimport { createMemoryHistory, createLocation } from 'history';import { match } from 'react-router';const history = createMemoryHistory();const path = `/route/:id`;const match: match<{ id: string }> = { isExact: false, path, url: path.replace(':id', '1'), params: { id: "1" }};const location = createLocation(match.url);test('shallow render', () => { const wrapper = shallow( <MyComponent history={history} location={location} match={match} /> ); expect(wrapper).toMatchSnapshot();});
CAUTION Do not use this to test implementation detail, it can be tempting but it will cause you a lot of pain should you want to refactor.
Making a helper for this would probably be the best way to make this re-usable.
import { createLocation, createMemoryHistory } from 'history';import { match as routerMatch } from 'react-router';type MatchParameter<Params> = { [K in keyof Params]?: string };export const routerTestProps = <Params extends MatchParameter<Params> = {}> (path: string, params: Params, extendMatch: Partial<routerMatch<any>> = {}) => { const match: routerMatch<Params> = Object.assign({}, { isExact: false, path, url: generateUrl(path, params), params }, extendMatch); const history = createMemoryHistory(); const location = createLocation(match.url); return { history, location, match }; };const generateUrl = <Params extends MatchParameter<Params>> (path: string, params: Params): string => { let tempPath = path; for (const param in params) { if (params.hasOwnProperty(param)) { const value = params[param]; tempPath = tempPath.replace( `:${param}`, value as NonNullable<typeof value> ); } } return tempPath; };
Now we can just use the routerTestProps
function in our tests
const { history, location, match } = routerTestProps('/route/:id', { id: '1' });
A gentleman by the name of Timmy Huang provided a solution that involves a simple mock...
const routeComponentPropsMock = { history: {} as any, location: {} as any, match: {} as any,}
I tried this using Jest and it worked. My component had this signature...
export const MyComponent: React.FC<RouteComponentProps> = ({location}:RouteComponentProps) => {
My basic test to confirm the component loads then looked like this...
function renderMyComponent() { return render( <MyComponent {...routeComponentPropsMock}/> );}
I have been looking for a good solution to this. I was hoping I could do it in the mapStateToProps function or something simular, but have not been able to do this yet.
The best I could do was mock this out and pass in the match, location and history. I used the following:
import { RouteComponentProps } from 'react-router'import { match } from 'react-router-dom';import {UnregisterCallback, Href} from 'history'export function getMockRouterProps<P>(data: P) { var location: { hash: "", key: "", pathname: "", search: "", state: {} }; var props: RouteComponentProps<P> = { match: { isExact: true, params: data, path: "", url: "" }, location: location, history: { length:2, action:"POP", location: location, push: () => {}, replace: () => {}, go: (num) => {}, goBack: () => {}, goForward: () => {}, block: (t) => { var temp: UnregisterCallback = null; return temp; }, createHref: (t) => { var temp: Href = ""; return temp; }, listen: (t) => { var temp: UnregisterCallback = null; return temp; } }, staticContext: { } }; return props;}
Then in my test I did:
var routerProps = getMockRouterProps<ReduxTestComponentProps>(null); const wrapper = mount<ReduxTestComponent, ReduxTestComponentState>( <ReduxTestComponent history={routerProps.history} location={routerProps.location} match={routerProps.match} isLoadingTodo={false} todos={todos} addAsyncTodoActionDispatch={() => mockTodoAddDispatch()} deleteTodoActionDispatch={() => mockTodoDeleteDispatch()} /> );