Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 731
Phil Lucks Avatar asked Apr 30 '20 23:04

Phil Lucks


2 Answers

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.

like image 84
Florian Motteau Avatar answered Sep 17 '22 20:09

Florian Motteau


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>
like image 20
Phil Lucks Avatar answered Sep 20 '22 20:09

Phil Lucks