T
TanStack•3y ago
conventional-tan

Vitest rendering an empty div when RouterProvider is used

I have found that when unit testing with Vitest, if I pass a RouterProvider to the render function, the result is always a empty div, i.e:
<body>
<div />
</body>
<body>
<div />
</body>
I've tried to follow a couple of examples of wrappers that have been posted here and on GitHub, but they all have the same result. I've created a minimal reproduction here: https://stackblitz.com/edit/tanstack-router-1wnwna?file=src%2Fmain.test.tsx&view=editor Just run npm run test to see what I mean. Any advice is greatly appreciated.
Dani Bednarski
StackBlitz
Router - Empty div rendered in vitest - StackBlitz
Run official live example code for Router Basic, created by Tanstack on StackBlitz
10 Replies
adverse-sapphire
adverse-sapphire•3y ago
Can confirm, I am currently facing the same issue
harsh-harlequin
harsh-harlequin•3y ago
Not sure if this helps, but when I added a wait function for 0ms, I managed to get it working. I took the above example and filled in some missing configuration and dependecies.
harsh-harlequin
harsh-harlequin•3y ago
harsh-harlequin
harsh-harlequin•3y ago
Once it takes a (milli)second to actually render the application, you should be able to begin testing on it. If your are working from the original example, you should remember that the rootRoute needs to render the outlet component.
const rootRoute = new RootRoute({ componenet: () => <Outlet /> })
const rootRoute = new RootRoute({ componenet: () => <Outlet /> })
adverse-sapphire
adverse-sapphire•3y ago
I have a more complex setup where I add react-query as well. I use a different render file that look like this:
/**
* Testing setup
* @see https://medium.com/@janesfrontenddiary/a-reusable-way-to-test-react-components-that-make-use-of-react-context-a82150344c46
*/

import {
QueryClient,
QueryClientProvider,
} from '@tanstack/react-query';
import { render } from '@testing-library/react';
import { ReactElement } from 'react';

export function setupQueryClient() {
return new QueryClient({
defaultOptions: {
queries: {
retry: false,
},
},
});
}

export const customRender = (ui: ReactElement) => {

const Wrapper = ({ children }: { children: React.ReactNode }) => {
return (
<QueryClientProvider client={setupQueryClient()}>
{children}
</QueryClientProvider>
)
}

return render(ui, { wrapper: Wrapper })
}

export { customRender as render }
/**
* Testing setup
* @see https://medium.com/@janesfrontenddiary/a-reusable-way-to-test-react-components-that-make-use-of-react-context-a82150344c46
*/

import {
QueryClient,
QueryClientProvider,
} from '@tanstack/react-query';
import { render } from '@testing-library/react';
import { ReactElement } from 'react';

export function setupQueryClient() {
return new QueryClient({
defaultOptions: {
queries: {
retry: false,
},
},
});
}

export const customRender = (ui: ReactElement) => {

const Wrapper = ({ children }: { children: React.ReactNode }) => {
return (
<QueryClientProvider client={setupQueryClient()}>
{children}
</QueryClientProvider>
)
}

return render(ui, { wrapper: Wrapper })
}

export { customRender as render }
I got it to work in storybook by creating a hook withRoute but for some reason I cannot figure out how to do it with vitest This is how it works in Storybook
import React from 'react';
import type { Preview } from '@storybook/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { createMemoryHistory, RootRoute, Route, Router, RouterProvider } from '@tanstack/react-router';

const rootRoute = new RootRoute();
const indexRoute = new Route({ getParentRoute: () => rootRoute, path: "/" });
const memoryHistory = createMemoryHistory({ initialEntries: ["/"] });
const routeTree = rootRoute.addChildren([indexRoute]);
const router = new Router({ routeTree, history: memoryHistory });

const withRouter: Preview["decorators"][0] = (Story, context) => {
return <RouterProvider router={router} defaultComponent={() => <Story {...context} />} />;
};

import '../src/index.css';

const preview: Preview = {
parameters: {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
},
decorators: [
(story) => {
const queryClient = new QueryClient();
return (
<QueryClientProvider client={queryClient}>
{withRouter(story)}
</QueryClientProvider>
)
}
]
};

export default preview;
import React from 'react';
import type { Preview } from '@storybook/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { createMemoryHistory, RootRoute, Route, Router, RouterProvider } from '@tanstack/react-router';

const rootRoute = new RootRoute();
const indexRoute = new Route({ getParentRoute: () => rootRoute, path: "/" });
const memoryHistory = createMemoryHistory({ initialEntries: ["/"] });
const routeTree = rootRoute.addChildren([indexRoute]);
const router = new Router({ routeTree, history: memoryHistory });

const withRouter: Preview["decorators"][0] = (Story, context) => {
return <RouterProvider router={router} defaultComponent={() => <Story {...context} />} />;
};

import '../src/index.css';

const preview: Preview = {
parameters: {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
},
decorators: [
(story) => {
const queryClient = new QueryClient();
return (
<QueryClientProvider client={queryClient}>
{withRouter(story)}
</QueryClientProvider>
)
}
]
};

export default preview;
I got it to work but it looks horrible 😅 I have to wrap that wait inside act becuase it mutate the state so it look horrible but it works This is the test setup file:
/**
* Testing setup
* @see https://medium.com/@janesfrontenddiary/a-reusable-way-to-test-react-components-that-make-use-of-react-context-a82150344c46
*/

import {
QueryClient,
QueryClientProvider,
} from '@tanstack/react-query';
import { render } from '@testing-library/react';
import { ReactElement } from 'react';
import { createMemoryHistory, RootRoute, Route, Router, RouterProvider } from '@tanstack/react-router';

const rootRoute = new RootRoute();
const indexRoute = new Route({ getParentRoute: () => rootRoute, path: "/" });
const memoryHistory = createMemoryHistory({ initialEntries: ["/"] });
const routeTree = rootRoute.addChildren([indexRoute]);
const router = new Router({ routeTree, history: memoryHistory });

const withRouter = (Story: React.ReactNode) => {
return <RouterProvider router={router} defaultComponent={() => Story} />;
};

export function setupQueryClient() {
return new QueryClient({
defaultOptions: {
queries: {
retry: false,
},
},
});
}

export const customRender = (ui: ReactElement) => {

const Wrapper = ({ children }: { children: React.ReactNode }) => {
return (
<QueryClientProvider client={setupQueryClient()}>
{withRouter(children)}
</QueryClientProvider>
)
}

return render(ui, { wrapper: Wrapper })
}

export { customRender as render }

export function wait(ms: number): Promise<void> {
return new Promise<void>((resolve) => {
setTimeout(() => {
resolve();
}, ms);
});
}
/**
* Testing setup
* @see https://medium.com/@janesfrontenddiary/a-reusable-way-to-test-react-components-that-make-use-of-react-context-a82150344c46
*/

import {
QueryClient,
QueryClientProvider,
} from '@tanstack/react-query';
import { render } from '@testing-library/react';
import { ReactElement } from 'react';
import { createMemoryHistory, RootRoute, Route, Router, RouterProvider } from '@tanstack/react-router';

const rootRoute = new RootRoute();
const indexRoute = new Route({ getParentRoute: () => rootRoute, path: "/" });
const memoryHistory = createMemoryHistory({ initialEntries: ["/"] });
const routeTree = rootRoute.addChildren([indexRoute]);
const router = new Router({ routeTree, history: memoryHistory });

const withRouter = (Story: React.ReactNode) => {
return <RouterProvider router={router} defaultComponent={() => Story} />;
};

export function setupQueryClient() {
return new QueryClient({
defaultOptions: {
queries: {
retry: false,
},
},
});
}

export const customRender = (ui: ReactElement) => {

const Wrapper = ({ children }: { children: React.ReactNode }) => {
return (
<QueryClientProvider client={setupQueryClient()}>
{withRouter(children)}
</QueryClientProvider>
)
}

return render(ui, { wrapper: Wrapper })
}

export { customRender as render }

export function wait(ms: number): Promise<void> {
return new Promise<void>((resolve) => {
setTimeout(() => {
resolve();
}, ms);
});
}
afraid-scarlet
afraid-scarlet•2y ago
I still get <body> <div /> </body> with this solution. Did you improve this somehow?
correct-apricot
correct-apricot•2y ago
ditto on this as well if anyone has a proper solution
conscious-sapphire
conscious-sapphire•2y ago
I am looking for a solution to integrate the Router into Storybook as well
blank-aquamarine
blank-aquamarine•2y ago
@dohomi - Did you figure out how to integrate with Storybook?
conscious-sapphire
conscious-sapphire•2y ago
I used a similar file like Aly and it worked so far

Did you find this page helpful?