Jest needs ts-jest for TypeScript, @swc/jest for speed, babel-jest for ESM, and still doesn't support import.meta. Your test suite takes 45 seconds because Jest transforms every file through Babel.
What if your test runner was built on Vite — instant TypeScript, native ESM, and watch mode that reruns in milliseconds?
That's Vitest.
Quick Start
npm install -D vitest
// package.json
{ "scripts": { "test": "vitest" } }
That's the entire setup. No config file needed for most projects.
Jest-Compatible API
import { describe, it, expect, vi, beforeEach } from "vitest";
describe("UserService", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("creates a user with valid data", async () => {
const user = await createUser({ name: "Aleksej", email: "dev@test.com" });
expect(user).toMatchObject({
name: "Aleksej",
email: "dev@test.com",
});
expect(user.id).toBeDefined();
});
it("throws on duplicate email", async () => {
await createUser({ name: "First", email: "same@test.com" });
await expect(
createUser({ name: "Second", email: "same@test.com" })
).rejects.toThrow("Email already exists");
});
});
Looks like Jest. Runs like Vite. Most Jest tests work in Vitest with minimal changes.
Speed Comparison
| Operation | Jest | Vitest |
|---|---|---|
| Cold start (500 tests) | 12s | 1.8s |
| Watch mode rerun | 3.2s | 0.15s |
| TypeScript tests | Needs ts-jest | Native |
| ESM imports | Experimental | Native |
Features Jest Doesn't Have
In-Source Testing
// utils.ts — tests live WITH the code
export function slugify(text: string): string {
return text.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
}
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");
});
});
}
Tests are tree-shaken from production builds. They only run during vitest.
Type Testing
import { expectTypeOf, test } from "vitest";
test("function return types", () => {
expectTypeOf(getUser).returns.toMatchTypeOf<Promise<User>>();
expectTypeOf(getUser).parameter(0).toMatchTypeOf<string>();
});
Browser Mode
// vitest.config.ts
export default defineConfig({
test: {
browser: {
enabled: true,
name: "chromium",
provider: "playwright",
},
},
});
Run tests in a real browser — DOM APIs, localStorage, Web Workers all work natively.
Mocking
import { vi } from "vitest";
// Mock modules
vi.mock("./database", () => ({
db: {
users: {
findMany: vi.fn().mockResolvedValue([{ id: "1", name: "Test" }]),
create: vi.fn().mockImplementation((data) => ({ id: "2", ...data })),
},
},
}));
// Mock timers
vi.useFakeTimers();
vi.advanceTimersByTime(1000);
// Spy on functions
const spy = vi.spyOn(console, "log");
myFunction();
expect(spy).toHaveBeenCalledWith("expected output");
Coverage
vitest --coverage
Built-in coverage with v8 or istanbul — no extra packages needed.
Migration From Jest
Most changes are mechanical:
| Jest | Vitest |
|---|---|
jest.fn() |
vi.fn() |
jest.mock() |
vi.mock() |
jest.spyOn() |
vi.spyOn() |
jest.config.js |
vitest.config.ts |
@jest/globals |
vitest |
When to Choose Vitest
Choose Vitest when:
- You use Vite for your project (shared config)
- TypeScript tests without extra setup
- Watch mode speed matters (instant feedback)
- You want ESM support without hacks
Stick with Jest when:
- Large existing Jest test suite (migration cost)
- You need Jest's mature snapshot testing ecosystem
- Your project uses webpack (Vitest is Vite-based)
The Bottom Line
Vitest is Jest rebuilt for the Vite era. Same familiar API, 10x faster execution, native TypeScript and ESM. If you're starting a new project, there's little reason to choose Jest.
Start here: vitest.dev
Need custom data extraction, scraping, or automation? I build tools that collect and process data at scale — 78 actors on Apify Store and 265+ open-source repos. Email me: Spinov001@gmail.com | My Apify Actors
Top comments (0)