19

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

1
  • 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. Commented Nov 19, 2022 at 6:41

5 Answers 5

21

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();
Sign up to request clarification or add additional context in comments.

1 Comment

For anyone looking to fix this in vitest, the instructions are slightly different but very clear in the docs
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
    }
});

Comments

4

The below code works like charm but if you are looking for a more elaborated answer please refer to https://stackoverflow.com/a/77460901/2640467

// 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: () => {},
  },
}));

Comments

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
};

Comments

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();

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.