Remixを使って開発しているプロジェクトにて、@remix-run/react
から公開されている<Form />
を内部で使用しているコンポーネントをテストしようとしました。
import { Form } from "@remix-run/react";
export function Item() {
return (
<div>
<Form method="post">
<input type="hidden" name="id" value={id} />
...
<button type="submit" />
</Form>
</div>
);
}
これをvitest
と@testing-library/react
を使ってテストしようとしたところ、次のようなエラーが発生しました。
The above error occurred in the <FormImpl> component:
...
Error: useNavigate() may be used only in the context of a <Router> component.
この<Item />
コンポーネントでは直接useNavigate()
は呼び出していませんが、おそらく<Form />
の内部で呼び出しているために、このようなエラーが発生するのだと推測されます。
これを回避するために、<BrowserRouter />
でラップすれば良いと思うかもしれませんが、そう簡単にはいきません。
もし仮に<BrowserRouter />
でラップしてみてもError: You must render this element in a remix route element
というエラーが発生します。
なので、ここは大人しくVitestの機能を使ってモックを行うことにします。
vitest.config.tsにてtest.setupFiles
にセットアップファイルのパスを設定します。
/// <reference types="vitest" />
/// <reference types="vite/client" />
...
export default defineConfig({
...
test: {
...
setupFiles: ["./test/setup-test-env.ts"],
...
},
});
指定したセットアップファイルにて、@remix-run/react
から公開されているuseNavigate()
と<Form />
をモックします。
import { installGlobals } from "@remix-run/node";
// import { useNavigate } from "@remix-run/react";
import "@testing-library/jest-dom/extend-expect";
import type React from "react";
installGlobals();
vi.mock("@remix-run/react", () => {
const useNavigate = vi.fn();
const form = vi.fn().mockImplementation(({ children }: { children: React.ReactElement, }) => {
return children
});
return {
useNavigate,
Form: form,
}
})
特徴としては、このセットアップファイルはtsx
ではないので、<Form />
をモックするときはchildren
を引数に取る関数を渡す必要があります。そしてその関数の中でchildren
を返すようにします。
もう一度テストをしてみたところ、無事にテストが通りました。
Test Files 1 passed (1)
Tests 1 passed (1)
Start at 13:05:23
Duration 2.33s (transform 631ms, setup 669ms, collect 458ms, tests 14ms)
PASS Waiting for file changes...
あまり同じようなエラーに遭遇した人がいないためか、これの問題に正面から解説されている記事がありませんでした。
僕が費やした数時間が、誰かの助けにでもなれば幸いです。
参照記事