Why does useParams during test return undefined in test env with wrapper?

I have seen this in 2 different code bases now and am stumped because it works fine in the actual browser but not the tests: if a component uses the useParams hook, the hook throws an error in the test:

Error: Uncaught [TypeError: Cannot destructure property accountId of ‘undefined’ or ‘null’.]

I am using React Functional Component, React Testing Library and React-Router:

// component:

const Overview: FC = () => {
  const location = useLocation();
  console.log(location) // see below
  const { accountId } = useParams();
  console.log(accountId) // see below

  ... rest
}

the console logs appear to have found the params properly:

console.log src/screens/Overview/index.tsx:66
accountId: nodata

console.log src/screens/Overview/index.tsx:64
location: { pathname: ‘/mix/overview/nodata’,
search: ”,
hash: ”,
state: undefined,
key: ‘bn6zvv’ }

// testing setup with wrapper as recommended in the RTL docs

function renderWithProviders(
  ui,
  {
    route = '/',
    params = routes.root, 
    history = createMemoryHistory({ initialEntries: [route] }),
  } = {},
  apolloMocks
) {
  console.log("route:", route) // see below
  console.log("params:", params) // see below
  return {
    ...render(
      <Provider store={mockProviderStore}>
        <Router history={history}>
          <MockedProvider mocks={apolloMocks}>
            <Route path={params}> // tried to set path to "params" not "route" so the slug of /url/:accountId is properly set
             {ui}
            </Route>
          </MockedProvider>
        </Router>
      </Provider>
    ),
    history,
  };
}

the result of the console log in the test-utils file looks correct:

console.log src/test-utils/index.js:19
route: /mix/overview/nodata

console.log src/test-utils/index.js:20
params: /mix/overview/:accountId

// test itself

test('it works', async () => {
    const { findByText } = renderWithProviders(<Overview />, {
      route: routes.overview('1234567890'), // => path/1234567890
      params: routes.overview(), // => path/:accountId
    });

    await findByText('loading configs...');

    await waitForElementToBeRemoved(() => getByText('loading configs...'));

    await findByText('View your stuff now. It works!');
  });

I am trying the recommended work around from #kentdodds to use await in the tests which lets state changes to settle correctly.

What is it about the React-Router hook which isn’t picking up the route params correctly?

Answers:

Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.

Method 1

You could also mock query parameters using Jest like so :

import ReactRouter from 'react-router'

describe(() => {
  test('...', () => {
    jest.spyOn(ReactRouter, 'useParams').mockReturnValue({ id: '1234' });
    // id = "1234" in your tested component
  });
});

See https://stackoverflow.com/a/58180976/2295549 to mock more of react-router.

Method 2

Make sure, even if you have a wrapper with Router/Route like suggested, that the component you want to test gets wrapped with a Route with params:

<Route path="/mix/config/:param1/:param2/:param3" >
        <Config />
      </Route>


All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x