ReferenceError: Request is not defined when testing with React Router v6.4
Asked Answered
B

5

14

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

Borderer answered 19/11, 2022 at 6:11 Comment(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<string> { SearchResultsScreen.tsx::14 where during the unit test Request it seems is undefined. I haven't an explanation now why though.Kujawa
B
7

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
    }
});
Borderer answered 19/11, 2022 at 16:25 Comment(0)
A
18

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();
Aerothermodynamics answered 13/12, 2022 at 22:4 Comment(1)
For anyone looking to fix this in vitest, the instructions are slightly different but very clear in the docsRunty
B
7

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
    }
});
Borderer answered 19/11, 2022 at 16:25 Comment(0)
S
3

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
};
Strength answered 23/5, 2023 at 17:18 Comment(0)
A
2

The below code works like charm but if you are looking for a more elaborated answer please refer to https://mcmap.net/q/799352/-referenceerror-request-is-not-defined-when-testing-with-react-router-v6-4

// react-router-dom uses Remix router and since
// Remix router is failing for some reason around the Request obj
global["Request"] = jest.fn().mockImplementation(() => ({
  signal: {
    removeEventListener: () => {},
    addEventListener: () => {},
  },
}));
Aboveground answered 10/11, 2023 at 15:1 Comment(0)
A
1

I managed to solve it by installing the polyfills for Node (Remix):

npm i -D @remix-run/node

Then inside the test file installed the globals such as "fetch", "Response", "Request" and "Headers":

import { installGlobals } from "@remix-run/node";
...

installGlobals();

I did have also to install whatwg-fetch since remix-router/node have a dependency to the fetch global object and I am using jsdom as test environment but if your using node as test environment, You need not to worry.

whole setup configuration file

import { RouterProvider, createMemoryRouter } from "react-router-dom";

import "@testing-library/jest-dom";
import "whatwg-fetch";
import { render, screen, waitFor } from "@testing-library/react";
import { installGlobals } from "@remix-run/node";

// This installs globals such as "fetch", "Response", "Request" and "Headers".
installGlobals();
Aboveground answered 10/11, 2023 at 14:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.