Laravel+Inertia.js+Vitestで `TypeError: Cannot read properties of undefined (reading 'createProvider')` が出た時の対応
Inertia.jsという存在を最近知り、面白いと思ったので、フロントエンドは苦手ではありますが、色々試していました。
Inertia.js - The Modern Monolith
そういった状況で表題のエラーが出たので、調べた備忘録です。
一旦やりたかったことは出来ましたが、正攻法かは分かりません。
2022/11/05:別の対応法を確認出来たので追記
環境
- Laravel: 9.19
- @inertiajs/inertia: 0.11.1
- @inertiajs/inertia-vue3: 0.6.0
- vue: 3.2.4
- vite: 3.0.0
- vitest: 0.24.3
エラー内容
下記のようなファイルを resources/js/Pages
以下に作成します。
// Welcome.vue <script setup> import { onMounted } from "vue"; import { Head } from "@inertiajs/inertia-vue3"; onMounted(() => { console.log("Welcome Page mounted"); }); </script> <template> <Head> <title>Welcome</title> </Head> <h1>Welcome Inertia.js</h1> </template>
// Welcome.test.js // @vitest-environment happy-dom import { mount } from '@vue/test-utils' import { describe, expect, test } from 'vitest' import Welcome from './Welcome.vue' describe('Screen Display', () => { test('Display `Welcome` message', () => { const wrapper = mount(Welcome) expect(wrapper.text()).toContain('Welcome') }) })
この状態で、vitestを実行すると以下のエラーになります。
❯❯ npm run test > test > vitest run RUN v0.24.3 /laravel_inertia ❯ resources/js/Pages/Welcome.test.js (1) ❯ Screen Display (1) × Display `Welcome` message ❯ Proxy.data node_modules/@inertiajs/inertia-vue3/dist/index.js:1:5942 ❯ applyOptions node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:3384:34 ❯ finishComponentSetup node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:7257:9 ❯ setupStatefulComponent node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:7168:9 ❯ setupComponent node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:7090:11 ❯ mountComponent node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:5448:13 ❯ processComponent node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:5423:17 ❯ patch node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:5027:21 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ FAIL resources/js/Pages/Welcome.test.js > Screen Display > Display `Welcome` message TypeError: Cannot read properties of undefined (reading 'createProvider') ❯ mountChildren node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:5210:13 ❯ processFragment node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:5382:13 ❯ patch node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:5020:17 ❯ ReactiveEffect.componentUpdateFn [as fn] node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:5562:21 ❯ ReactiveEffect.run node_modules/@vue/reactivity/dist/reactivity.cjs.js:191:25 ❯ instance.update node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:5669:56 ❯ setupRenderEffect node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:5683:9 ❯ mountComponent node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:5465:9 ❯ processComponent node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:5423:17 ❯ patch node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:5027:21 ❯ ReactiveEffect.componentUpdateFn [as fn] node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:5562:21 ❯ ReactiveEffect.run node_modules/@vue/reactivity/dist/reactivity.cjs.js:191:25 ❯ instance.update node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:5669:56 ❯ setupRenderEffect node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:5683:9 ❯ mountComponent node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:5465:9 ❯ processComponent node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:5423:17 ❯ patch node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:5027:21 ❯ render node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:6183:13 ❯ mount node_modules/@vue/runtime-core/dist/runtime-core.cjs.js:4417:25 ❯ app.mount node_modules/@vue/runtime-dom/dist/runtime-dom.cjs.js:1523:23 ❯ Proxy.mount node_modules/@vue/test-utils/dist/vue-test-utils.cjs.js:8002:18 ❯ resources/js/Pages/Welcome.test.js:8:20 Test Files 1 failed (1) Tests 1 failed (1) ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯ Start at 01:05:07 Duration 1.74s (transform 592ms, setup 0ms, collect 145ms, tests 13ms)
調査
記事確認
TypeError: Cannot read properties of undefined (reading 'createProvider')
というエラーになっているので、とりあえずこちらのエラー文をググってみると、laracastsのページが出ます。
ただ、残念ながら解決はしていない。
また、inertia-laravelのGitHub issueにも同様の質問が投げられていますが、こちらもまだ回答なし。
ソースコード確認
inertiajs/inertia
のソースコードで createProvider
を検索してみると、vueやreactのライブラリの各パッケージ用と大元の処理の部分に記載があるようでした。
inertia/head.ts at 66fabda56d2f5e2f645073f619c25cb69b760e91 · inertiajs/inertia · GitHub
この辺りの処理を眺めるとなんとなく、 <title>
のタグとかを探したりしていそうです。
vitest動作確認
テストの処理をデバッグしてみます。
console.log
を追記してコンポーネントのHTML内容を確認。
// @vitest-environment happy-dom import { mount } from '@vue/test-utils' import { describe, expect, test } from 'vitest' import Welcome from './Welcome.vue' describe('Screen Display', () => { test('Display `Welcome` message', () => { const wrapper = mount(Welcome) console.log(wrapper.html()) expect(wrapper.text()).toContain('Welcome') }) })
stdout | resources/js/Pages/Welcome.test.js > Screen Display > Display `Welcome` message Welcome Page mounted <h1>Welcome Inertia.js</h1>
bodyの中身だけ表示されますね。
Welcomeコンポーネントだけ確認しているので、当たり前ではありますが、headの内容が入っていないのはなんか怪しいような気が...?
検証
以下のようにVitestでテストを行いたいファイルと、 Head
を読み込むファイルを分割しました。
// Welcome.vue <script setup> import { onMounted } from "vue"; onMounted(() => { console.log("Welcome Page mounted"); }); </script> <script> import Layout from "./Layout.vue"; export default { layout: Layout, } </script> <template> <h1>Welcome Inertia.js</h1> </template>
// Lauout.vue <script> import { Head } from "@inertiajs/inertia-vue3"; export default { components: { Head, } } </script> <template> <Head> <title>Welcome</title> </Head> <main> <slot /> </main> </template>
結果、vitestが通りました。
❯❯ npm run test > test > vitest run RUN v0.24.3 /laravel_inertia stdout | resources/js/Pages/Welcome.test.js > Screen Display > Display `Welcome` message Welcome Page mounted ✓ resources/js/Pages/Welcome.test.js (1) Test Files 1 passed (1) Tests 1 passed (1) Start at 01:17:37 Duration 1.66s (transform 618ms, setup 0ms, collect 197ms, tests 13ms)
推測
vitestでテストしたいファイルと、 Head
でhead要素を書き換える処理を行なっている部分を別ファイルに分割することで、対応は出来ました。
ということで、 Head
が処理する内容がvitestで描画して確認する範囲に存在しないため、出ているエラーだったんじゃないかと推測しました。
ただ、これがあっているのかはちょっと分かりません...。
GitHubのissueにはとりあえずこの記事の対応でコメントしておいたので、もし間違っていた場合は詳しい方が指摘していただけることを願います。
やっぱりフロントエンドは苦手ですが、今回は調べていてちょっと楽しかったです。
2022/11/05追記
分離してテストから外すという方法がなんとなく気持ち悪かったので、諦めずに他の方法を探していました。
すると、Next.jsの方で同じような記事を発見。こちらはjestでの記述ですが、ほぼ同じ記述で再現することが出来ました。
next/headを使ったmetadataを React Testing Library でテストする
How to test metadata using jest and react library test · Discussion #11060 · vercel/next.js · GitHub
Reactで書いていますが、要するにHead部分をmock化すれば良いので、Vueでも似たような方法で書けると思います。
// Title.tsx import React from 'react'; import { Head } from '@inertiajs/inertia-react'; type TitlePrpps = { title: string; }; function Title(props: TitlePrpps) { const { title } = props; return ( <Head> <title>{title ? `${title} - MyPage` : 'MyPage'}</title> </Head> ); } export default Title;
// Title.test.tsx // @vitest-environment happy-dom import React from 'react'; import '@testing-library/jest-dom'; import { render } from '@testing-library/react'; import { describe, expect, test, vi } from 'vitest'; import Title from '@/Components/Layout/Title'; vi.mock('@inertiajs/inertia-react', () => ({ Head: ({ children }: { children: Array<React.ReactElement> }) => ( <>{children}</> ) })); describe('Screen Display', () => { test('Display `title - MyPage` title', async () => { render(<Title title="test" />, { container: document.head }); expect(document.head.querySelector('title')?.innerHTML).toEqual( 'test - MyPage' ); }); });