Testing with Vitest
Quick Start
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
globals: true,
environment: "jsdom",
setupFiles: ["./tests/setup.ts"],
coverage: {
provider: "v8",
reporter: ["text", "html"],
thresholds: { global: { branches: 80, functions: 80, lines: 80 } },
},
},
});
import { afterEach, vi } from "vitest";
import { cleanup } from "@testing-library/vue";
import "@testing-library/jest-dom/vitest";
afterEach(() => { vi.clearAllMocks(); cleanup(); });
Features
| Feature |
Description |
Reference |
| Unit Testing |
Fast isolated tests with describe/it/expect |
Vitest API |
| Mocking |
Module, function, and timer mocking with vi |
Mocking Guide |
| Snapshot Testing |
Component and data structure snapshots |
Snapshot Testing |
| Component Testing |
Vue/React component testing with Testing Library |
Vue Test Utils |
| Coverage Reports |
V8/Istanbul coverage with thresholds |
Coverage |
| Parallel Execution |
Multi-threaded test runner |
Test Runner |
Common Patterns
Unit Testing with Parametrization
import { describe, it, expect, test } from "vitest";
describe("String Utils", () => {
test.each([
["hello", 3, "hel..."],
["hi", 10, "hi"],
["test", 4, "test"],
])('truncate("%s", %d) returns "%s"', (str, len, expected) => {
expect(truncate(str, len)).toBe(expected);
});
});
Mocking Modules and Services
import { describe, it, expect, vi, beforeEach, Mock } from "vitest";
import { UserService } from "@/services/user";
import { apiClient } from "@/lib/api";
vi.mock("@/lib/api", () => ({
apiClient: { get: vi.fn(), post: vi.fn() },
}));
describe("UserService", () => {
beforeEach(() => vi.clearAllMocks());
it("fetches user from API", async () => {
const mockUser = { id: "1", name: "John" };
(apiClient.get as Mock).mockResolvedValue({ data: mockUser });
const user = await new UserService().getUser("1");
expect(apiClient.get).toHaveBeenCalledWith("/users/1");
expect(user).toEqual(mockUser);
});
});
Vue Component Testing
import { describe, it, expect, vi } from "vitest";
import { mount } from "@vue/test-utils";
import Button from "@/components/Button.vue";
describe("Button", () => {
it("emits click event", async () => {
const wrapper = mount(Button, { slots: { default: "Click" } });
await wrapper.trigger("click");
expect(wrapper.emitted("click")).toHaveLength(1);
});
it("is disabled when loading", () => {
const wrapper = mount(Button, { props: { loading: true } });
expect(wrapper.attributes("disabled")).toBeDefined();
});
});
Testing Async Operations with Timers
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
describe("Debounce", () => {
beforeEach(() => vi.useFakeTimers());
afterEach(() => vi.useRealTimers());
it("delays function execution", () => {
const fn = vi.fn();
const debouncedFn = debounce(fn, 100);
debouncedFn();
expect(fn).not.toHaveBeenCalled();
vi.advanceTimersByTime(100);
expect(fn).toHaveBeenCalledTimes(1);
});
});
Best Practices
| Do |
Avoid |
| Use descriptive test names explaining behavior |
Testing implementation details |
| Test behavior, not internal state |
Sharing state between tests |
| Use test.each for multiple similar cases |
Using arbitrary timeouts |
| Mock external dependencies |
Over-mocking internal modules |
| Keep tests focused and isolated |
Duplicating test coverage |
| Write tests alongside code |
Ignoring flaky tests |
References