[v6] feature proposal: Allow to inject history || Discussion: Testing alternatives #8241
-
One of the breaking changes since version v4-v5 with respect to V6 is the way to build the Router. In older versions, the router accepts a "history" object as a parameter: // v4-v5
import React from "react";
import ReactDOM from "react-dom";
import { createBrowserHistory } from "history";
const customHistory = createBrowserHistory();
ReactDOM.render(<Router history={customHistory} />, node); while in v6 this option is not longer available. Although it seems silly, this was tremendously useful when doing automatic tests, especially with respect to routes. In our case we have a test-wrapper that injects a router into the tests and allows us to easily test different behaviors. Example of test using {history}: it('should redirect to the 500 error page when server returns a 500 error', async () => {
// Wrapper over mock service worker to spy on the next server request
mockServer.stubJSONResponse({
method: 'post',
path: '*/v1/developer/signin',
response: { code: 123, message: 'dummy_error_message' },
status: 500,
});
// We render the page using the testRender utility
const { history } = testRender(LoginPage);
// Act on the inputs
await type(screen.queryByLabelText('Email') as HTMLInputElement, '[email protected]');
await type(screen.queryByLabelText('Password') as HTMLInputElement, 'dummy_password');
// Click on the login button. Try to login against the server.
userEvent.click(screen.getByRole('button', { name: 'Log in' }));
// We know the server response, we expect the location to be the 500 error page
return waitFor(() => {
expect(history.location.pathname).toEqual(ERROR_ROUTE);
});
}); Fragment of the testRender function: export default function testRender(
Component: React.ElementType,
{ route = '/', path = '/', session = null, props = {} }: ITestRenderOptions = DEFAULT_VALUE
) {
// Create a fresh history and set the current URL
const history = createMemoryHistory();
history.push(route);
// Render the given component using the HOC
const WrappedComponent = WithTestRouter({ Component, history, path, route, session, props });
// Render the component using testing library
const renderResults = render(<WrappedComponent />);
return { ...renderResults, history, WrappedComponent };
}
export function WithTestRouter({ Component, props, ...options }: ITestRouterProps) {
return function TestRouter() {
return (
<Router history={options.history}>
<Switch>
<Route path={options.path}>
<Component {...props} />
</Route>
</Switch>
</Router>
);
};
} We assume that retrieving this feature is not in the roadmap. If so, what would be the appropriate alternative for testing? cc @kentcdodds I'm sure you have something to say here :D |
Beta Was this translation helpful? Give feedback.
Replies: 4 comments 10 replies
-
I don't recommend using the memory history. Your tests should use the same router your app uses. I answered this question in office hours last week right about here: https://youtu.be/GessP-lJoww?t=270 Hope that helps! |
Beta Was this translation helpful? Give feedback.
-
Lol just spammed Kent on twitter and it turns he already replied. <3 I'll try to do it BrowserHistory spying on window.location 🧠 |
Beta Was this translation helpful? Give feedback.
-
I use a This way I can avoid testing what the history library is doing and just scope my tests to testing what the router is doing. If there's a bug in the history library, that's a separate issue. |
Beta Was this translation helpful? Give feedback.
-
Is there any way how to inject custom history to Router in react-router 6? With custom frameworks and deeper integrations this is crucial. Edit: // "HistoryRouter" implementation
import * as React from 'react'
import type {BrowserHistory} from 'history'
import {Router} from 'react-router-dom'
export interface HistoryRouterProps {
history: BrowserHistory
basename?: string
children?: React.ReactNode
}
export function HistoryRouter({
basename,
children,
history,
}: HistoryRouterProps) {
let [state, setState] = React.useState({
action: history.action,
location: history.location,
})
React.useLayoutEffect(() => history.listen(setState), [history])
return (
<Router
basename={basename}
children={children}
location={state.location}
navigationType={state.action}
navigator={history}
/>
)
}
// Use
import {createBrowserHistory} from 'history'
const history = createBrowserHistory()
ReactDOM.render(
<HistoryRouter history={history}>
...
</HistoryRouter>
, root) https://codesandbox.io/s/2hdb8?file=/src/index.js Edit 2: |
Beta Was this translation helpful? Give feedback.
I don't recommend using the memory history. Your tests should use the same router your app uses. I answered this question in office hours last week right about here: https://youtu.be/GessP-lJoww?t=270
Hope that helps!