Vue cookbook · Beginner

Test components with Vitest and Vue Test Utils

UI changes break forms and buttons without anyone noticing until production.

Create a free account to save this page, take notes and track your progress. Sign in

Approach

Mount components in a DOM environment (happy-dom or jsdom), interact through the rendered output, await DOM updates, and assert on text, attributes and emitted events. Provide plugins like Pinia in global.plugins.

Solution

// LoginForm.test.ts
import { mount, flushPromises } from "@vue/test-utils";
import { createPinia } from "pinia";
import { describe, expect, it, vi } from "vitest";
import LoginForm from "./LoginForm.vue";

describe("LoginForm", () => {
  it("shows an error for a wrong password", async () => {
    vi.stubGlobal("fetch", vi.fn().mockResolvedValue(new Response(null, { status: 401 })));
    const wrapper = mount(LoginForm, { global: { plugins: [createPinia()] } });

    await wrapper.get("input[type=email]").setValue("[email protected]");
    await wrapper.get("input[type=password]").setValue("wrong");
    await wrapper.get("form").trigger("submit");
    await flushPromises();

    expect(wrapper.get("[role=alert]").text()).toBe("Invalid email or password");
    expect(wrapper.emitted("success")).toBeUndefined();
  });

  it("emits success after logging in", async () => {
    vi.stubGlobal("fetch", vi.fn().mockResolvedValue(Response.json({ name: "Ana" })));
    const wrapper = mount(LoginForm, { global: { plugins: [createPinia()] } });

    await wrapper.get("input[type=email]").setValue("[email protected]");
    await wrapper.get("input[type=password]").setValue("correct-horse");
    await wrapper.get("form").trigger("submit");
    await flushPromises();

    expect(wrapper.emitted("success")?.[0]).toEqual([{ name: "Ana" }]);
  });
});

Pitfalls

  • Asserting on component internals (wrapper.vm.state) instead of what users see.
  • Forgetting flushPromises() after async work triggered by events.
  • Snapshot tests of whole pages that change constantly and get updated without review.

Variations

  • @pinia/testing's createTestingPinia stubs actions and lets you set initial state.
  • Use Playwright component or end-to-end tests for flows across pages.

Further reading: Vue Test Utils

Reference: https://test-utils.vuejs.org/guide/

More Vue recipes

Was this page helpful?