🍇

ReferenceError: Request is not defined

2024/09/05に公開

例えば、React Routerが使われたコードをjestでテストすると、タイトルのエラーが発生すると思います。

原因としては、下記になります。
https://github.com/jsdom/jsdom/issues/1724
上を読むとどうやら、単に、jsdomにRequestオブジェクトが実装されてないというだけになるかと思います。
issue自体も7年前にopenになってますし、RequestやFetch_APIもそんな新しい感じがしないので、jsdom、ちょっと大丈夫かなっていう感じは少しします。

↑のissue内にも対応方法がいろいろ載ってますが、
React Routerのドキュメントにもヒントが載ってます。
https://reactrouter.com/en/main/routers/picking-a-router#testing
jest実行時に、whatwg-fetchをpolyfillとして、加えてあげれば良さそうです。

ただ、↑を見ると、node.jsがv18以降だとpolyfillが必要無いような書かれ方になってますが、Testing Libraryあたりと一緒にテストする場合は、v18以降だったとしても、jsdomを使う関係で必要になります。

さらに細かく調べる

そもそもどんなコードで発生するエラーかというと、
こんな↓コードがあった場合に、

hello.tsx
import { Link } from "react-router-dom";

export default function Hello() {
  return (
    <>
      <div>Hello</div>
      <Link to="/home?page=hoge">Click me</Link>
    </>
  )
}

こんなテストコード↓があったとして、

hello.test.tsx
import {render, screen} from '@testing-library/react'
import '@testing-library/jest-dom'
import Hello from '../hello'
import { createMemoryRouter, RouteObject, RouterProvider } from 'react-router-dom'

test('loads and displays greeting', async () => {
  const FAKE_EVENT = { name: "test event" };
  const routes: RouteObject[] = [
    {
      path: "/",
      element: <Hello />,
      loader: () => FAKE_EVENT, // ← ここがあるかないかで変わる
    }
  ];

  const router = createMemoryRouter(routes, {
    initialEntries: ["/"],
    initialIndex: 1,
  });

  // ARRANGE
  render(<RouterProvider router={router} />)

  // ACT
  const e = await screen.findByText('Hello')

  expect(e).toBeTruthy()
})

実行すると、表題のようなエラーが発生します。

$ yarn test src/routes/__test__/hello.test.tsx
yarn run v1.22.22
$ jest src/routes/__test__/hello.test.tsx
 FAIL  src/routes/__test__/hello.test.tsx
  ✕ loads and displays greeting (1026 ms)

  ● loads and displays greeting

    ReferenceError: Request is not defined

      17 |   ];
      18 |
    > 19 |   const router = createMemoryRouter(routes, {
         |                                    ^
      20 |     initialEntries: ["/"],
      21 |     initialIndex: 1,
      22 |   });

      at createClientSideRequest (node_modules/@remix-run/router/router.ts:5056:3)
      at createClientSideRequest (node_modules/@remix-run/router/router.ts:1551:19)
      at Object.startNavigation [as initialize] (node_modules/@remix-run/router/router.ts:1100:7)
      at initialize (node_modules/react-router/index.ts:322:6)
      at src/routes/__test__/hello.test.tsx:19:36
      at src/routes/__test__/hello.test.tsx:8:71
      at Object.<anonymous>.__awaiter (src/routes/__test__/hello.test.tsx:4:12)
      at Object.<anonymous> (src/routes/__test__/hello.test.tsx:8:48)

↑で発生した行数とちょっと違いますが、github上だと下記のあたりになるかと思います。
https://github.com/remix-run/react-router/blob/9afac15d8cbe30b37d0f9e8b89c9f1e430dfe35a/packages/router/router.ts#L5093
本当に、Requestが使われてます。

ちなみに、jsdomじゃなくて、デフォルトのnodeの場合は

jestのデフォルトの実行環境は、nodeですが、例えば、jest.config.jsをこんな感じで設定すると思います。

jest.config.js
/** @type {import('ts-jest').JestConfigWithTsJest} **/
export default {
  testEnvironment: "node",
  transform: {
    "^.+.tsx?$": ["ts-jest",{}],
  },
  setupFiles: ['<rootDir>/setupJest.ts'],
};

そして、さっきのhello.test.tsxをテストすると

$ yarn test src/routes/__test__/hello.test.tsx
yarn run v1.22.22
$ jest src/routes/__test__/hello.test.tsx
 FAIL  src/routes/__test__/hello.test.tsx
  ✕ loads and displays greeting (13 ms)

  ● loads and displays greeting

    The error below may be caused by using the wrong test environment, see https://jestjs.io/docs/configuration#testenvironment-string.
    Consider using the "jsdom" test environment.
    
    ReferenceError: document is not defined

      23 |
      24 |   // ARRANGE
    > 25 |   render(<RouterProvider router={router} />)
         |         ^
      26 |
      27 |   // ACT
      28 |   const e = await screen.findByText('Hello')

      at render (node_modules/@testing-library/react/dist/pure.js:239:5)
      at src/routes/__test__/hello.test.tsx:25:9
      at src/routes/__test__/hello.test.tsx:8:71
      at Object.<anonymous>.__awaiter (src/routes/__test__/hello.test.tsx:4:12)
      at Object.<anonymous> (src/routes/__test__/hello.test.tsx:8:48)

こんなエラーが発生します。

nodeの環境だと、documentが実装されてないんですね。
深くは調べてないですが、そりゃそうかなという気がします。

逆に、nodeでは、v18以降で、Requestは実装されているらしいので、こんな↓感じのテストコードが成功します。

request.test.ts
test('Request exists', () => {
  const r = new Request('https://example.com')
  expect(r).toBeTruthy()
})

ここまでで使用したコードは下記に置いてあります。
https://github.com/na8esin/reactrouter-tutorial/tree/7606489ba79d85bf60a67982e92c5397fad42808
ここまでのところで発生しているエラーが発生しないコードになっています。

jsdomの最新版でもダメなの?

issueが閉じられてないので、ダメだと思うんですが、念の為調べてみました。
※そもそもjsdom自体を詳細に調べてみたかった。

jestを使う場合は、jest-environment-jsdomを経由して使うと思いますが、バージョンが若干古かったです。
https://github.com/jestjs/jest/blob/bd1c6db7c15c23788ca3e09c919138e48dd3b28a/packages/jest-environment-jsdom/package.json#L26

現時点の最新版はここでした。
https://github.com/jsdom/jsdom/releases/tag/25.0.0

なので、新しいディレクトリを作成して、yarn addなどして、下記のようにプロジェクトを作りました。
https://github.com/na8esin/jsdom-practice/tree/1aea966adc21c48f971732362dc84e8540bea854

そして下記のようなコードを実行すると

const jsdom = require("jsdom");
const { JSDOM } = jsdom;

const dom = new JSDOM(`<body>
  <div id="content"></div>
  <script>new Request();</script>
</body>`, { runScripts: "dangerously" });

こんな感じでエラーが発生するのでやっぱりダメみたいです。

jsdom-practice$ node request.js            
Error: Uncaught [ReferenceError: Request is not defined]
しくみのテックブログ

Discussion