はじめに
ChakraUI v3を使用しているプロジェクトでVitestを導入したところ、テスト実行時にChakraUI特有の問題でエラーが発生したので解決方法を記載します。
前提条件
- Vitestの導入は完了
- react-testing-libraryは導入済みで、ChakraUI以外であればコンポーネントをレンダリングしてのテストが可能な状態
コンポーネントのrender時にエラー
例えば以下のような、testing-libraryのrender()を使用してコンポーネントの内容を確認するシンプルなテストですが、実行するとエラーが発生します。
import { render } from "@testing-library/react";
test("タイトルが表示されること", () => {
render(<App />);
expect(document.title).toEqual("トップページ");
});
ContextError: useContext returned `undefined`. Seems you forgot to wrap component within <ChakraProvider />
Seems you forgot to wrap component within <ChakraProvider />と書かれているように、ChakraUIではコンポーネント全体を<Provider>(ChakraUI v2では<ChakraProvider>)で囲わなければエラーとなりますが、render()ではそのコンポーネントしかレンダリングしません。
そのため、以下のようにすればとりあえずエラーは解消します。
import { render } from "@testing-library/react";
import { Provider } from "@/components/ui/provider";
test("タイトルが表示されること", () => {
render(
<Provider>
<App />
</Provider>
);
expect(document.title).toEqual("トップページ");
});
これでも良いのですが、毎回コンポーネントを<Provider>で囲うのも面倒なので、公式ではrender関数をラップした新しい関数をライブラリとして作成し、その中でコンポーネントを自動で囲うようにするやり方が推奨されています。
具体的には、以下のような関数を適当なフォルダ配下に作成し
import { Provider } from "@/components/ui/provider"
import { render as rtlRender } from "@testing-library/react"
export function render(ui: React.ReactNode) {
return rtlRender(<>{ui}</>, {
wrapper: (props: React.PropsWithChildren) => (
<Provider>{props.children}</Provider>
),
})
}
テストファイル側ではそのファイルをimportしてrender関数として使用することで、特に毎回<Provider>を書くことなくテストが実行できるようになります。
import { render } from "@/test_utils/render";
test("タイトルが表示されること", () => {
render(<App />);
expect(document.title).toEqual("トップページ");
});
window.matchMediaが存在しないエラー
renderのエラーが解決したところで、テストを普通に実行すると以下のようなエラーが発生します。
TypeError: window.matchMedia is not a function
windowオブジェクトはブラウザ環境だとデフォルトで使用できますが、nodeでは存在しないため、おそらくコンポーネントのレンダリングで使用されているwindow.matchMediaが見つからずにエラーとなってしまいます。
そのため、テストの設定ファイルに、このオブジェクトのモックを設定してあげる必要があります。
今回は、vitest-setup.tsに以下を追記しました。
// matchMedia mock
Object.defineProperty(window, "matchMedia", {
writable: true,
value: vi.fn().mockImplementation((query) => ({
matches: false,
media: query,
onchange: null,
addListener: vi.fn(),
removeListener: vi.fn(),
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
dispatchEvent: vi.fn(),
})),
});
こちらはrenderの解決策と同様に、公式サイトからのコピペとなっています。
公式サイトのセットアップファイル設定令にはwindow.matchMedia以外のモック設定も書かれているため、もし他のJavascriptオブジェクトについて同様のエラーが出た場合、こちらを参照すればある程度解決できそうです。
おわりに
Chakra UI公式でもv3ではJestよりVitestを推奨している旨の記述があるため、ドキュメントも充実しておりほぼ公式サイトからの引用となっていますが、最初は地味に戸惑ったため記事を書きました。
他にもVitestの導入は記事の記載が古かったりすることも多く、地味に詰まる点もありましたが、最終的にはVitestやReact Testing Libraryの公式ドキュメントを見て解決した部分が多かったので、やはりできるだけ公式ドキュメントを確認するようにしようと思った次第です。