I am trying to setup an test file to render a route/page on my application. I'm trying to wrap everything with Redux and Router, and this is what I have:
import React from 'react';
import { render } from 'react-testing-library';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import reducer from '../../store/reducer';
import {Link, Route, Router, Switch} from 'react-router-dom'
import {createMemoryHistory} from 'history'
import ViewNode from '../Pages/ViewNode';
const customRender = (
ui,
{
route = '/',
history = createMemoryHistory({ initialEntries: [route] }),
initialState,
store = createStore(reducer, initialState),
...options
} = {}
) => ({
...render(
<Provider store={store}>
<Router history={history}>{ui}</Router>
</Provider>,
options
),
history,
});
test('can render with redux and router', () => {
const { getByTestId } = customRender(
<Route path="/server/:env/:nodeName">
<ViewNode />
</Route>,
{
route: '/server/prod/some.server.name.com',
}
);
expect(getByTestId('page-content')).toBeVisible()
})
Then I get the following error:
Error: Uncaught [TypeError: Cannot read property 'params' of undefined]
The reason this is throwing the error is because it cannot find the React Router params. It's failing in the component constructor when Im initializing the state:
this.state = {
modal: false,
activeTab: '1',
imageStatus: "loading",
env: props.match.params.env, //failing here
nodeName: props.match.params.nodeName,
environments: props.environments,
}
It seems like it isn't wrapping the router properly with my implementation above.
How would I properly wrap my page component with Redux and Router so that it can get these router params?
You have placed your <ViewNode />
component inside a Route
but forgot to pass on the props it receives. That is why props.match
is undefined in your component.
You can do this instead:
<Route path="/server/:env/:nodeName">
{props => <ViewNode {...props} />}
</Route>
Basically, you can use one of the 3 ways to render something with a <Route>
.
Here is a working example:
import React from 'react'
import {Route, Router} from 'react-router-dom'
import {createMemoryHistory} from 'history'
import {render, fireEvent} from '@testing-library/react'
import {createStore} from 'redux'
import {Provider, connect} from 'react-redux'
function reducer(state = {count: 0}, action) {
switch (action.type) {
case 'INCREMENT':
return {
count: state.count + 1,
}
case 'DECREMENT':
return {
count: state.count - 1,
}
default:
return state
}
}
class Counter extends React.Component {
increment = () => {
this.props.dispatch({type: 'INCREMENT'})
}
decrement = () => {
this.props.dispatch({type: 'DECREMENT'})
}
render() {
return (
<div>
<div data-testid="env-display">{this.props.match.params.env}</div>
<div data-testid="location-display">{this.props.location.pathname}</div>
<div>
<button onClick={this.decrement}>-</button>
<span data-testid="count-value">{this.props.count}</span>
<button onClick={this.increment}>+</button>
</div>
</div>
)
}
}
const ConnectedCounter = connect(state => ({count: state.count}))(Counter)
function customRender(
ui,
{
initialState,
store = createStore(reducer, initialState),
route = '/',
history = createMemoryHistory({initialEntries: [route]}),
} = {},
) {
return {
...render(
<Provider store={store}>
<Router history={history}>{ui}</Router>
</Provider>,
),
store,
history,
}
}
test('can render with redux and router', () => {
const {getByTestId, getByText} = customRender(
<Route path="/server/:env/:nodeName">
{props => <ConnectedCounter {...props} />}
</Route>,
{
route: '/server/prod/some.server.name.com',
},
)
expect(getByTestId('env-display')).toHaveTextContent('prod')
expect(getByTestId('location-display')).toHaveTextContent(
'/server/prod/some.server.name.com',
)
fireEvent.click(getByText('+'))
expect(getByTestId('count-value')).toHaveTextContent('1')
})
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With