#Unit tests with React Router 6.4+

1 messages · Page 1 of 1 (latest)

small wyvern
#

I have non-top-level components that use useParams and useSearchParams all over the place to avoid prop drilling, and lots of nested routes. I'm trying to test a dialog that uses some react router hooks and I'm trying to mock stuff using window.history.pushState but I don't understand how this is supposed to work with a router that can't have children.

My common pattern is something like

import {render} from '@testing-library/react';
import {beforeEach} from 'vitest';

beforeEach(() => {
  window.history.pushState({}, '', '/route/:routeParam?searchParam=foo')
});

function customRender(children: ReactNode) {
  return render(
    <Provider1> // if my component needs providers, make a "customRender" function
      <Provider2>
        {children}
      </Provider2>
    </Provider1>
  );
}

it('should work', () => {
  customRender(<ComponentThatUsesParams />)
  ...
});

Since RouterProvider doesn't accept children, how can I get my components to exist in a context where useParams/useSearchParams will work correctly and not require a direct mock? It could be that my app structure is problematic. Perhaps the useParams in non-top-level components is a bad pattern and I should be prop drilling everywhere? But that seems bad. I have dialogs with deeply nested components that sometimes useNavigate to close the dialog via navigation, which is arguably a bad pattern, but does that mean it's completely untestable? 🤔

I've seen answers regarding use memory router instead of browser router (why? I thought jsdom lets you do the window.history stuff?), but that is not my problem. I'd have this problem no matter what kind of router I used because RouterProvider doesn't accept children.

Feels like I'm misunderstanding something fundamental.

#

Should I do something like

customRender(children) {
  const router = createBrowserRouter([{
    element: children,
    path: 'path/with/:params/:i/:need?foo=bar'
  }]);

  return (
    <Provider1>
      <Provider2>
        <RouterProvider router={router} />
      <Provider2>
    <Provider1>
  );
}
raven crater
halcyon cosmos
#

Yep - just wrap your components in createMemoryRouter/RouterProvider: ```jsx
let router = createMemoryRouter([{
path: '/',
Component() {
return (
<Provider1>
<Provider2>
<Component />
</Provider1>
</Provider2>
);
}
}]);

render(<RouterProvider router={router} />

small wyvern
#

why can't I use createBrowserRouter? It seems to work in my tests

halcyon cosmos
#

You can - it just adds more complexity because you've then got to setup js-dom or some other DOM emulation

small wyvern
#

Well if I'm testing components don't I need that anyway?

halcyon cosmos
#

Not unless you're testing things that use DOM apis like document or window. None of the RR features (loaders, actions, fetchers, etc) need it