Vitest is the blazing fast unit test framework powered by Vite — it's Jest-compatible, runs ESM natively, and is 2-10x faster. Completely free.
Why Vitest Over Jest?
- Vite-powered — uses Vite's transform pipeline, instant HMR for tests
-
Jest-compatible — same API (
describe,it,expect), easy migration -
ESM native — no more
babel-jestorts-jestconfigs - In-source testing — write tests next to your code
- Browser mode — run tests in real browsers
- UI dashboard — visual test explorer
Quick Start
npm install -D vitest
// package.json
{
"scripts": {
"test": "vitest",
"test:ui": "vitest --ui",
"test:coverage": "vitest run --coverage"
}
}
Writing Tests
// math.test.ts
import { describe, it, expect } from "vitest";
import { add, multiply, divide } from "./math";
describe("Math utilities", () => {
it("adds two numbers", () => {
expect(add(2, 3)).toBe(5);
expect(add(-1, 1)).toBe(0);
});
it("multiplies two numbers", () => {
expect(multiply(3, 4)).toBe(12);
});
it("throws on division by zero", () => {
expect(() => divide(10, 0)).toThrow("Cannot divide by zero");
});
});
Async Testing
import { describe, it, expect, vi } from "vitest";
describe("API client", () => {
it("fetches users", async () => {
const users = await fetchUsers();
expect(users).toHaveLength(2);
expect(users[0]).toHaveProperty("name");
});
it("handles API errors", async () => {
vi.spyOn(global, "fetch").mockRejectedValueOnce(new Error("Network error"));
await expect(fetchUsers()).rejects.toThrow("Network error");
});
});
Mocking
import { describe, it, expect, vi, beforeEach } from "vitest";
import { sendEmail } from "./email";
import * as mailer from "./mailer";
// Mock entire module
vi.mock("./mailer");
describe("Email service", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("sends welcome email", async () => {
vi.mocked(mailer.send).mockResolvedValue({ id: "msg-1" });
const result = await sendEmail("user@test.com", "Welcome!");
expect(mailer.send).toHaveBeenCalledWith({
to: "user@test.com",
subject: "Welcome!",
});
expect(result.id).toBe("msg-1");
});
it("retries on failure", async () => {
vi.mocked(mailer.send)
.mockRejectedValueOnce(new Error("timeout"))
.mockResolvedValueOnce({ id: "msg-2" });
const result = await sendEmail("user@test.com", "Hello");
expect(mailer.send).toHaveBeenCalledTimes(2);
expect(result.id).toBe("msg-2");
});
});
Snapshot Testing
import { it, expect } from "vitest";
it("renders user card", () => {
const html = renderUserCard({ name: "Alice", role: "Admin" });
expect(html).toMatchInlineSnapshot(`
"<div class=\\"card\\">
<h2>Alice</h2>
<span class=\\"badge\\">Admin</span>
</div>"
`);
});
it("serializes config correctly", () => {
const config = generateConfig({ env: "production" });
expect(config).toMatchSnapshot();
});
In-Source Testing (Unique to Vitest)
// utils.ts
export function slugify(text: string): string {
return text
.toLowerCase()
.replace(/\s+/g, "-")
.replace(/[^a-z0-9-]/g, "");
}
// Tests live IN the source file!
if (import.meta.vitest) {
const { describe, it, expect } = import.meta.vitest;
describe("slugify", () => {
it("converts spaces to hyphens", () => {
expect(slugify("hello world")).toBe("hello-world");
});
it("removes special characters", () => {
expect(slugify("Hello, World!")).toBe("hello-world");
});
it("handles multiple spaces", () => {
expect(slugify("foo bar baz")).toBe("foo-bar-baz");
});
});
}
Tests are tree-shaken out of production builds.
Testing React Components
// Button.test.tsx
import { describe, it, expect } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";
import Button from "./Button";
describe("Button", () => {
it("renders text", () => {
render(<Button>Click me</Button>);
expect(screen.getByText("Click me")).toBeDefined();
});
it("calls onClick", () => {
const onClick = vi.fn();
render(<Button onClick={onClick}>Click</Button>);
fireEvent.click(screen.getByText("Click"));
expect(onClick).toHaveBeenCalledOnce();
});
});
Coverage
// vitest.config.ts
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
coverage: {
provider: "v8", // or 'istanbul'
reporter: ["text", "json", "html"],
exclude: ["node_modules/", "test/"],
thresholds: {
lines: 80,
functions: 80,
branches: 80,
},
},
},
});
Vitest vs Jest vs Mocha vs Node Test Runner
| Feature | Vitest | Jest | Mocha | Node --test |
|---|---|---|---|---|
| Speed | Fastest | Fast | Medium | Fast |
| ESM support | Native | Experimental | Plugin | Native |
| TypeScript | Native (Vite) | ts-jest | ts-node | tsx |
| Watch mode | Instant (HMR) | Good | Plugin | Basic |
| In-source tests | Yes | No | No | No |
| Browser mode | Yes | jsdom | jsdom | No |
| UI | Built-in | No | No | No |
| Coverage | Built-in | Built-in | Plugin | Built-in |
| Config | vite.config | jest.config | .mocharc | None |
Need to scrape data from any website and get it in structured JSON? Check out my web scraping tools on Apify — no coding required, results in minutes.
Have a custom data extraction project? Email me at spinov001@gmail.com — I build tailored scraping solutions for businesses.
Top comments (0)