はじめに
Vite + React + TypeScript + Chakra UIのプロジェクトでJestのテストを実行したところ、Chakra UI特有のエラーが複数発生しました。この記事では、発生したエラーとその対処法をまとめます。
前提
- Jestとtesting-libraryの導入が完了していること
- Chakra UIを使用していること
発生したエラーと対処法
エラー①:Providerで囲んでいない
エラー内容
ContextError: useContext returned undefined.
Seems you forgot to wrap component within
原因
実際のアプリでは main.tsx で <Provider> がアプリ全体を囲んでいますが、テスト内ではそれがないため、Chakra UIの useContext が undefined を返してエラーになります。
対処法
テスト内でmain.tsx と同様に<Provider> でラップすれば解決します。
しかし、テストが増えてくると毎回 <Provider> を書くのが面倒なので、カスタム render を作るのがおすすめです。
src/__tests__/test-utils.tsx を作成:
import { render } from "@testing-library/react";
import { Provider } from "../components/ui/provider";
const customRender = (ui: React.ReactElement) => {
return render(
<Provider>{ui}</Provider>
);
};
export { customRender as render };
テストでは通常の render の代わりにこれをインポートして使います:
import { screen } from "@testing-library/react";
import App from "../App";
import { render } from "./test-utils";
describe("App", () => {
test("タイトルがあること", () => {
render(<App />);
expect(screen.getByText("Hello World")).toBeInTheDocument();
});
});
※ test-utils.tsx はテストファイルではないため、jest.config.js に testMatch を設定してテスト対象から除外する必要があります。
testMatch: ["**/__tests__/**/*.spec.(ts|tsx)"],
エラー②:window.matchMedia is not a function
エラー内容
TypeError: window.matchMedia is not a function
原因
Chakra UIは内部でレスポンシブ対応のために window.matchMedia を使用していますが、Jestのテスト環境(jsdom)には window.matchMedia が存在しないためエラーになります。
対処法
jest.setup.ts に window.matchMedia のモック(ダミー実装)を追加します。
// window.matchMediaのモック
// jsdom環境にはwindow.matchMediaが存在しないため、ダミーの実装を定義する
// Chakra UIなどのUIライブラリがレスポンシブ対応のために内部で使用しているため必要
Object.defineProperty(window, "matchMedia", {
writable: true,
value: jest.fn().mockImplementation((query) => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(),
removeListener: jest.fn(),
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});
エラー③:structuredClone is not defined
エラー内容
ReferenceError: structuredClone is not defined
原因
Chakra UIが内部で structuredClone を使用していますが、Jestのjsdom環境にはデフォルトで structuredClone が存在しないためエラーになります。
対処法
jest.setup.ts にポリフィル(代替実装)を追加します。
// structuredCloneのポリフィル(代替実装)
// jsdom環境にはstructuredCloneが存在しないため、JSON変換で代用する
// Chakra UIなどのライブラリが内部でstructuredCloneを使っている場合に必要
if (typeof structuredClone === "undefined") {
global.structuredClone = (val: any) => JSON.parse(JSON.stringify(val));
}
structuredClone が存在しない場合のみ、JSON.parse(JSON.stringify(...)) で代用する簡易的なポリフィルです。
jest.setup.ts の全体像
上記の対処法をすべて入れた jest.setup.ts はこうなります。
import "@testing-library/jest-dom";
import { config } from "dotenv";
config({ quiet: true });
if (typeof structuredClone === "undefined") {
global.structuredClone = (val: any) => JSON.parse(JSON.stringify(val));
}
Object.defineProperty(window, "matchMedia", {
writable: true,
value: jest.fn().mockImplementation((query) => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(),
removeListener: jest.fn(),
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});