Test a Laravel and Vue app

Pest hits the Laravel routes. Vitest and Vue Test Utils hit one component. The starter kit does not ship a Vitest script until you add one.

For your AI agent

Copies a prompt. Paste it into the AI agent you already have open.

Read https://laravel-vuejs.space/test-laravel-vue.md
Pest hits the Laravel routes. Vitest and Vue Test Utils hit one component. The starter kit does not ship a Vitest script until you add one.
Do not claim the kit's package.json already has Vitest.
Do not mount posts/Index.vue and stub router.get just to see a heading.
Show the diff. Do not commit.

Next: TypeScript props on Laravel Vue pages

On this page · 7 sections
  1. Feature tests first
  2. A component test
  3. What not to mock
  4. Recap
  5. FAQ
  6. Next steps
  7. Sources

Your Pest suite is green and the page still renders blank, or your Vue tests pass while the query is wrong. Each side needs its own test, and neither should pretend to be the other. Here is the split, plus the Vitest setup the starter kit doesn’t include.

TL;DR

  • Pest + assertInertia: routes, validation, queries and the props Inertia receives.
  • Vitest + @vue/test-utils: markup that renders from props, with no server.
  • The kit has no Vitest: install vitest @vue/test-utils jsdom and add vitest.config.ts.
  • Don’t mount a page and stub Inertia’s router just to see a heading.

What you'll learn

  • What Pest covers and what Vitest covers
  • How to add Vitest to the starter kit
  • What not to mock

Who it's for: Laravel + Vue developers adding tests

Applies to: Laravel 13 · Pest · Vitest · @vue/test-utils · Vue 3

A Laravel and Vue app has two test runs because the two sides fail differently. Pest can prove a POST creates a row and that Inertia received posts.data. It does not mount the Vue page. Vitest can prove a component renders a title from a prop. It does not hit SQLite. The requirements name both Pest and Vitest. This page is how to add the Vue half without pretending the starter kit already has it.

Pest (PHP)Vitest (JS)
ProvesA POST creates a row; Inertia got posts.dataA component renders a title from a prop
Does notMount the Vue pageHit SQLite

Feature tests first#

The feature tests for create, an empty title, and search are already in the CRUD guide. Run those first.

bash
php artisan test --filter=PostsTest

Feature tests use the session stack. They do not need a CSRF token. assertInertia reads the props the controller returned. If posts.data is missing, the controller is still returning a plain array, which is the bug the CRUD page calls out.

A component test#

Heads up: Vitest is not in the kit

Do not claim the kit’s package.json already has Vitest. Add it.

bash
npm install -D vitest @vue/test-utils jsdom
vitest.config.tstypescript
import { defineConfig } from "vitest/config";
import vue from "@vitejs/plugin-vue";

export default defineConfig({
  plugins: [vue()],
  test: {
    environment: "jsdom",
  },
});

Keep this file next to the kit’s vite.config.ts. A component test should not boot Inertia’s router. Pass props and read the text.

resources/js/components/PostTitle.vuevue
<script setup lang="ts">
defineProps<{ title: string }>();
</script>

<template>
  <h2>{{ title }}</h2>
</template>
resources/js/components/PostTitle.test.tstypescript
import { mount } from "@vue/test-utils";
import { describe, expect, it } from "vitest";
import PostTitle from "./PostTitle.vue";

describe("PostTitle", () => {
  it("renders the title", () => {
    const wrapper = mount(PostTitle, {
      props: { title: "Hello Laravel 13" },
    });

    expect(wrapper.text()).toContain("Hello Laravel 13");
  });
});
package.jsonjson
{
  "scripts": {
    "test:unit": "vitest run"
  }
}

Run the first component test

bash
npm run test:unit

Gotcha: window at import time

jsdom is there so mount has a document. If the component reads window at import time, the test fails the same way server rendering fails. Move that read into onMounted, or pass the value in as a prop.

What not to mock#

Gotcha: brittle page mocks

Do not mount posts/Index.vue and stub router.get just to see a heading. That test breaks every time Inertia’s API moves, and it never notices a wrong SQL when. The Pest test owns the query. The Vitest test owns markup that has no server.

Typed props for the page itself are TypeScript on Vue pages. The stack you are testing is Inertia, a SPA, or Nuxt.

Recap#

  • Two runners, two jobs: Pest for the server, Vitest for components.
  • Add Vitest yourself; the kit does not ship it.
  • Component tests pass props, not routers.
  • Keep queries tested in Pest.

FAQ#

Does the Laravel Vue starter kit include Vitest?

No. It ships Pest for PHP. Install vitest, @vue/test-utils and jsdom to test Vue components.

How do I test Inertia props in Laravel?

Use assertInertia in a Pest feature test to check the component name and props like posts.data.

Should I mount Inertia pages in Vitest?

Usually not. Mount small components with props. Pages depend on Inertia’s router, and their data is better tested in Pest.

Next steps#

Sources#

Found this useful?

Ask questions, share what you built.

Join thousands of Laravel and Vue developers in our Facebook group, or get new posts in your Telegram feed. Sharing this article helps too.

Keep reading

Newsletter

Get the best of Laravel & Vue, once a month.

One short email with new guides, release notes worth reading and hand-picked community links. No spam, unsubscribe any time.