8

I am attempting to create unit tests using React Testing Library that click on React Router links to verify certain pages appear. I am using a very similar setup to the answer found here. When I run the test I get ReferenceError: Request is not defined. Since I am using a RouterProvider I can not follow the React Testing Library docs exactly.

I have my routes set up in a dedicated file:

export const routes: RouteObject[] = [{
    path: '/',
    element: <AppWrapper />,
    errorElement: <PageNotFoundScreen />,
    children: [{
        path: '/',
        element: <FeaturedSearchScreen />
    },{
        path: 'auth',
        element: <AuthScreen />,
        children: [{
            path: 'login',
            element: <LoginForm />
        },{
            path: 'signup',
            element: <SignUpForm />
        }]
        },{
            path: 'dashboard',
            element: <DashboardScreen />
        },{
            path: 'search',
            element: <SearchResultsScreen />,
            loader: searchLoader
        } 
    ]
}];

I then create a memory router in my test file

const router = createMemoryRouter(routes, {initialEntries: ['/']});
const user = userEvent.setup();
render(<RouterProvider router={router}/>);

I am using an Outlet in AppWrapper to render all of the children.

Expected

Tests pass

Results

Vitest caught 1 unhandled error during the test run.
This might cause false positive tests. Resolve unhandled errors to make sure your tests are not affected.

⎯⎯⎯⎯ Unhandled Rejection ⎯⎯⎯⎯⎯
ReferenceError: Request is not defined
 ❯ createRequest node_modules/@remix-run/router/router.ts:2654:3
 ❯ startNavigation node_modules/@remix-run/router/router.ts:886:19
 ❯ Object.navigate node_modules/@remix-run/router/router.ts:784:18
 ❯ push node_modules/react-router/lib/components.tsx:74:16
 ❯ navigate node_modules/react-router/lib/hooks.tsx:211:7
 ❯ internalOnClick node_modules/react-router-dom/index.tsx:727:9
 ❯ handleClick node_modules/react-router-dom/index.tsx:385:9
 ❯ HTMLUnknownElement.callCallback node_modules/react-dom/cjs/react-dom.development.js:4164:14
 ❯ HTMLUnknownElement.callTheUserObjectsOperation node_modules/jsdom/lib/jsdom/living/generated/EventListener.js:26:30
 ❯ innerInvokeEventListeners node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:350:25

When I render the initial screen, I am able to verify all components, so I know my set up is generally working. It fails awaiting the new page header. Feel free to look at the entire code on this branch https://github.com/Thenlie/Streamability/tree/43-feat-test-suite

Thenlie
  • 674
  • 8
  • 15
  • 1
    Since the router/components appear to render initially without issue I think the issue is with something that is rendered and doesn't manifest until the UI is interacted with. I'm almost certain the issue is with `export async function loader({ request }: { request: Request }): Promise {` [SearchResultsScreen.tsx::14](https://github.com/Thenlie/Streamability/blob/43-feat-test-suite/src/screens/SearchResultsScreen.tsx#L14) where during the unit test `Request` it seems is undefined. I haven't an explanation now why though. – Drew Reese Nov 19 '22 at 06:41

3 Answers3

9

I solved it using 'jest-fetch-mock' (https://www.npmjs.com/package/jest-fetch-mock)

For TypeScript/ES6 add the following lines to the start of your test case (before any other imports) in the setupTests.ts file.

import { enableFetchMocks } from 'jest-fetch-mock'
enableFetchMocks();
Dercilio
  • 101
  • 5
6

I ended up needing to create a setup file that defines Request. I found this through the React Router code

import { fetch, Request, Response } from "@remix-run/web-fetch";

if (!globalThis.fetch) {
  // Built-in lib.dom.d.ts expects `fetch(Request | string, ...)` but the web
  // fetch API allows a URL so @remix-run/web-fetch defines
  // `fetch(string | URL | Request, ...)`
  // @ts-expect-error
  globalThis.fetch = fetch;
  // Same as above, lib.dom.d.ts doesn't allow a URL to the Request constructor
  // @ts-expect-error
  globalThis.Request = Request;
  // web-std/fetch Response does not currently implement Response.error()
  // @ts-expect-error
  globalThis.Response = Response;
}

This can then be referenced in the testing config file, for me vitest.config.ts.

import { defineConfig } from 'vitest/config';

// https://vitest.dev/guide/#configuring-vitest
export default defineConfig({
    test: {
        environment: 'jsdom',
        setupFiles: './src/__tests__/setup.ts',
        globals: true,
        watch: false
    }
});
Thenlie
  • 674
  • 8
  • 15
0

The docs include this paragraph regarding testing:

Testing components that use React Router APIs is easiest with createMemoryRouter or instead of the routers you use in your app that require DOM history APIs.

Some of the React Router APIs internally use fetch, which is only supported starting from Node.js v18. If your project uses v17 or lower, you should add a fetch polyfill manually. One way to do that, is to install whatwg-fetch and add it to your jest.config.js file like so:

module.exports = {
    setupFiles: ["whatwg-fetch"],
    // ...rest of the config
};
elGusto
  • 56
  • 6