Learn Vue - Testing & Quality Assurance
Series/Learn Vue/Episode 16
Episode 16 of 24

Learn Vue - Testing & Quality Assurance

This episode covers quality assurance in Vue: unit testing with Vue Test Utils and Vitest, component and composable testing, integration testing with Playwright, plus static analysis with ESLint and type checking.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

The bigger an application gets, the more expensive bugs found late are. Testing isn't about chasing 100 percent coverage — it's about keeping trust: every code change can be proven not to break what already works.

Episode 16 covers quality assurance in Vue: component unit testing with Vue Test Utils and Vitest, composable testing, integration testing with Playwright for real user flows, and static analysis through ESLint and type checking.

Unit Testing with Vitest

Install and Setup

Vitest is a test runner integrated with Vite:

Install Vitest dan Vue Test Utils
npm install -D vitest @vue/test-utils jsdom
Konfigurasi di vite.config
export default defineConfig({
  test: { environment: "jsdom" },
});

defineConfig adds a test block so Vitest knows to use jsdom as the DOM environment.

Your First Component Test

JSTest komponen Counter
import { mount } from "@vue/test-utils";
import { describe, it, expect } from "vitest";
import Counter from "./Counter.vue";
 
describe("Counter", () => {
  it("menambah jumlah saat tombol diklik", async () => {
    const wrapper = mount(Counter);
    await wrapper.find("button").trigger("click");
    expect(wrapper.text()).toContain("1");
  });
});

mount(Counter) mounts the component in a simulated DOM, and wrapper.find("button").trigger("click") simulates a click. The assertion expect(...).toContain(...) verifies the component's behavior.

Testing Props and Event Interaction

Props and Emitted

A component that receives props and emits events can be fully tested:

JSKomponen yang diuji
<script setup>
const props = defineProps({ label: String });
const emit = defineEmits(["pilih"]);
</script>
 
<template>
  <button @click="emit('pilih', props.label)">{{ props.label }}</button>
</template>
JSTest props dan emits
import { mount } from "@vue/test-utils";
import { describe, it, expect } from "vitest";
import Tombol from "./Tombol.vue";
 
describe("Tombol", () => {
  it("mengirim label saat diklik", async () => {
    const wrapper = mount(Tombol, { props: { label: "Simpan" } });
    await wrapper.trigger("click");
    expect(wrapper.emitted("pilih")[0]).toEqual(["Simpan"]);
  });
});

wrapper.emitted("pilih") returns a list of the emitted event payloads. This proves the props and emits contract works correctly.

Testing Composables

Composables are tested by wrapping them in a dummy component:

JSTest composable
import { ref } from "vue";
import { mount } from "@vue/test-utils";
import { describe, it, expect } from "vitest";
import { useCounter } from "./useCounter";
 
function withSetup(fn) {
  let hasil;
  const App = {
    setup() {
      hasil = fn();
      return () => null;
    },
  };
  mount(App);
  return hasil;
}
 
describe("useCounter", () => {
  it("menambah nilai", () => {
    const { jumlah, tambah } = withSetup(() => useCounter(0));
    tambah();
    expect(jumlah.value).toBe(1);
  });
});

withSetup(fn) mounts the composable inside a dummy setup component so lifecycle hooks still run. Composables tested with this pattern can be reused safely.

Integration Testing with Playwright

Installing and Writing an E2E Test

Playwright tests the application in a real browser:

Install Playwright
npm install -D @playwright/test
npx playwright install chromium
JSTest alur login
import { test, expect } from "@playwright/test";
 
test("pengguna bisa login", async ({ page }) => {
  await page.goto("/login");
  await page.getByLabel("Email").fill("user@example.com");
  await page.getByLabel("Kata sandi").fill("rahasia123");
  await page.getByRole("button", { name: "Masuk" }).click();
  await expect(page).toHaveURL("/dashboard");
});

page.getByRole("button", { name: "Masuk" }) selects elements by their accessibility role, not fragile selectors. E2E tests prove the complete flow works in a real browser.

Static Analysis and Type Checking

ESLint and vue-tsc

Quality starts before tests even run:

Lint dan type check
npm run lint
npx vue-tsc --noEmit

vue-tsc --noEmit checks types across all Vue files, catching errors that aren't visible in the editor. Combine lint, type check, and unit tests into a single command run in CI.

Info

A healthy testing pyramid: many fast unit tests, some integration tests, and a few E2E tests for critical flows. Don't pile all the guarantees onto a single layer.

Summary

Episode 16 equipped you with a quality foundation: unit tests for components and composables with Vitest and Vue Test Utils, integration tests for user flows with Playwright, and static analysis plus type checking that catches errors early.

Key takeaways:

  • Vitest integrates directly with Vite.
  • mount and trigger simulate component interaction.
  • wrapper.emitted verifies emitted events.
  • Composables are tested through a dummy setup component.
  • Playwright tests real flows in a browser.
  • Lint, type check, and unit tests run in CI.

In the next episode 17, we'll cover accessibility and UX — the role of ARIA, keyboard navigation, semantic HTML, responsive design, and the basics of internationalization with i18n.

Learn Vue - Testing & Quality Assurance | Learn Vue