Did you ever figure this out? I'm getting something similar now and was curious what you ended up doing.
Vitest + Inertia.js errors when running tests on Page/Components
To preface, I am very new to writing client-side tests... There doesn't seem to be a lot of discussions/topics/documentation on using/setting up vitest with inertia.js - so I am having some trouble getting it running without errors.
I have setup my project with Laravel + Inertia.js + Vite + Vitest - and everything works fine in terms of compiling with vite - however I am having some trouble figuring out how to setup a basic vitest test for my UserIndex.vue page.
I found a topic related to testing with jest + inertia.js that I based my initial test setup off of.
If you look at my test file demo.test.ts below, you can see that I have setup a vi.mock for the @inertiajs/inertia-vue3 package - this was necessary because without it, it would return an error when trying to render the inertia.js <Head title="test"/> component - which is imported like so: import {Head, Link} from '@inertiajs/inertia-vue3' in my UserIndex.vue page. The same goes for using the inertia helpers like $inertia and $page to access page props and inertia methods for example.
The error was: TypeError: Cannot read properties of undefined (reading 'createProvider')
After, mocking the @inertiajs/inertia-vue3 I was able to bypass that particular error, however I am now running into another error related to the route mixin from inertia that is defined in the app.js - since it missing in the test environment, I am getting the following error regarding _ctx.route is not a function, and warnings about failing to resolve directive tooltip from the floating-vue package. The route() method is used to generate links which comes from the default inertia ziggy routing mixin defined in app.js.
I have found that when running the test using shallowMount instead of mount from @vue/test-utils - it will bypass the errors, but not the warnings, and it will pass the truthy test, but I am unable to get the text from the wrapper like expect(wrapper.text()).toContain('User') - this returns empty text and compares to 'User' which is false. After reading the documentation about mount/shallowMount - it doesn't look like shallowMount is what I need...
So my question is, am I on the right track with this test, should I be testing the entire page this way? If so, what can I do to resolve the route and directive errors? Should I be mocking all of these packages/directives?
Any direction would be very much appreciated. Please let me know if you would like to see any other code/files. Thanks!
_ctx.route error and warnings
stderr | tests/Client/demo.test.ts > UsersIndex > renders
[Vue warn]: Failed to resolve directive: tooltip
at <Index users= [] levels= {} ref="VTU_COMPONENT" >
at <VTUROOT>
[Vue warn]: Invalid vnode type when creating vnode: undefined.
at <Index users= [] levels= {} ref="VTU_COMPONENT" >
at <VTUROOT>
[Vue warn]: Property "route" was accessed during render but is not defined on instance.
at <Authenticated>
at <Index users= [] levels= {} ref="VTU_COMPONENT" >
at <VTUROOT>
[Vue warn]: Unhandled error during execution of render function
at <Authenticated>
at <Index users= [] levels= {} ref="VTU_COMPONENT" >
at <VTUROOT>
❯ tests/Client/demo.test.ts (1)
❯ UsersIndex (1)
× renders
FAIL tests/Client/demo.test.ts > UsersIndex > renders
TypeError: _ctx.route is not a function
❯ Proxy._sfc_render resources/js/Layouts/Authenticated.vue:94:30
92| <BreezeResponsiveNavLink :href="route('logout')" method="post" as="button">
93| Log Out
94| </BreezeResponsiveNavLink>
| ^
95| </div>
96| </div>
demo.test.ts
import { assert, describe, expect, it, vi } from 'vitest';
import { config, shallowMount, mount } from '@vue/test-utils';
import UsersIndex from '../../resources/js/Pages/Users/Index.vue';
vi.mock('@inertiajs/inertia-vue3',() => ({
__esModule: true,
...vi.importActual('@inertiajs/inertia-vue3'), // Keep the rest of Inertia untouched!
useForm: () => ({
/** Return what you need **/
/** Don't forget to mock post, put, ... methods **/
}),
usePage: () => ({
props: {
value: {
someSharedData: 'something',
},
},
})
}))
vi.mock('floating-vue', ()=> ({
__esModule: true,
...vi.importActual('floating-vue'),
}));
describe('UsersIndex', () => {
it('renders', () => {
const wrapper = mount(UsersIndex, {
props: {
users: [],
levels: {}
},
global: {
// mixins: [{methods: { route }}],
},
directives: {
// tooltip: { }
}
})
expect(wrapper).toBeTruthy()
})
})
vitest.config.ts. (my test config, which is used only when running vitest)
import { defineConfig } from 'vitest/config';
import { resolve } from 'path';
import vue from '@vitejs/plugin-vue';
export default defineConfig(({ command }) => ({
publicDir: false,
build: {
manifest: true,
},
resolve: {
alias: {
"@": resolve(__dirname, "resources/js"),
},
},
plugins: [
vue()
],
test: {
globals: true,
environment: 'jsdom'
}
}));
app.js
import "../css/app.scss";
import { createApp, h } from 'vue';
import { createInertiaApp } from '@inertiajs/inertia-vue3';
import { InertiaProgress } from '@inertiajs/progress';
import FloatingVue from 'floating-vue'
import Toast, {TYPE, POSITION} from "vue-toastification";
const appName = window.document.getElementsByTagName('title')[0]?.innerText || 'Laravel';
let asyncViews = () => {
return import.meta.glob("./Pages/**/*.vue");
};
createInertiaApp({
title: (title) => `${title} - ${appName}`,
resolve: async (name) => {
if (import.meta.env.DEV) {
return (await import(`./Pages/${name}.vue`)).default;
} else {
let pages = asyncViews();
const importPage = pages[`./Pages/${name}.vue`];
return importPage().then((module) => module.default);
}
},
setup({ el, app, props, plugin }) {
return createApp({ render: () => h(app, props) })
.use(plugin)
.use(FloatingVue)
.use(Toast)
.mixin({ methods: { route } })
.mount(el);
},
});
InertiaProgress.init({ color: '#4B5563' });
import "./bootstrap";
vite.config.ts
import { defineConfig } from 'vite';
import { resolve } from 'path';
import vue from '@vitejs/plugin-vue';
import laravel from 'vite-plugin-laravel';
export default defineConfig(({ command }) => ({
publicDir: false,
build: {
manifest: true,
},
resolve: {
alias: {
"@": resolve(__dirname, "resources/js"),
},
},
plugins: [
vue(),
laravel()
]
}));
Please or to participate in this conversation.