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.

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.
Vitest is a test runner integrated with Vite:
npm install -D vitest @vue/test-utils jsdomexport default defineConfig({
test: { environment: "jsdom" },
});defineConfig adds a test block so Vitest knows to use jsdom as the DOM environment.
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.
A component that receives props and emits events can be fully tested:
<script setup>
const props = defineProps({ label: String });
const emit = defineEmits(["pilih"]);
</script>
<template>
<button @click="emit('pilih', props.label)">{{ props.label }}</button>
</template>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.
Composables are tested by wrapping them in a dummy component:
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.
Playwright tests the application in a real browser:
npm install -D @playwright/test
npx playwright install chromiumimport { 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.
Quality starts before tests even run:
npm run lint
npx vue-tsc --noEmitvue-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.
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:
mount and trigger simulate component interaction.wrapper.emitted verifies emitted events.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.