DEV Community

Alex Spinov
Alex Spinov

Posted on

Vitest Has a Free Testing Framework That Outperforms Jest

Why Developers Are Switching From Jest to Vitest

Last year, a team I was consulting for spent 12 minutes waiting for their Jest test suite to finish. They had 2,000 tests across a Vite-based monorepo. After migrating to Vitest over a weekend, that same suite ran in under 2 minutes.

Vitest is a next-generation testing framework powered by Vite. It is blazing fast, Jest-compatible, and completely free.

What Makes Vitest Special

  • Vite-powered — uses Vite dev server for instant test transforms
  • Jest-compatible API — migrate with minimal changes
  • Native TypeScript support — no ts-jest configuration
  • Watch mode by default — re-runs only affected tests
  • Built-in coverage — via v8 or istanbul, no extra packages
  • In-source testing — write tests right next to your code
  • Workspace support — test monorepos with a single config

Getting Started

# Install Vitest
npm install -D vitest

# Add to package.json
# "scripts": { "test": "vitest" }
Enter fullscreen mode Exit fullscreen mode

Create vitest.config.ts:

import { defineConfig } from "vitest/config";

export default defineConfig({
  test: {
    globals: true,
    environment: "node",
    coverage: {
      provider: "v8",
      reporter: ["text", "json", "html"],
    },
  },
});
Enter fullscreen mode Exit fullscreen mode

Your First Test

// utils.ts
export function slugify(text: string): string {
  return text
    .toLowerCase()
    .replace(/[^\w\s-]/g, "")
    .replace(/[\s_]+/g, "-")
    .replace(/^-+|-+$/g, "");
}

export function truncate(str: string, maxLength: number): string {
  if (str.length <= maxLength) return str;
  return str.slice(0, maxLength - 3) + "...";
}
Enter fullscreen mode Exit fullscreen mode
// utils.test.ts
import { describe, it, expect } from "vitest";
import { slugify, truncate } from "./utils";

describe("slugify", () => {
  it("converts text to URL-friendly slug", () => {
    expect(slugify("Hello World")).toBe("hello-world");
    expect(slugify("Vitest Is Awesome!")).toBe("vitest-is-awesome");
  });

  it("handles special characters", () => {
    expect(slugify("price: $100")).toBe("price-100");
    expect(slugify("  extra  spaces  ")).toBe("extra-spaces");
  });
});

describe("truncate", () => {
  it("returns full string if under max length", () => {
    expect(truncate("short", 10)).toBe("short");
  });

  it("truncates with ellipsis", () => {
    expect(truncate("This is a long string", 10)).toBe("This is...");
  });
});
Enter fullscreen mode Exit fullscreen mode

Run with:

npx vitest
Enter fullscreen mode Exit fullscreen mode

Mocking Made Simple

Vitest has powerful mocking built in:

import { describe, it, expect, vi } from "vitest";

// Mock a module
vi.mock("./api", () => ({
  fetchUser: vi.fn().mockResolvedValue({ id: 1, name: "Alice" }),
}));

import { fetchUser } from "./api";
import { getUserDisplay } from "./user-service";

describe("getUserDisplay", () => {
  it("formats user data", async () => {
    const result = await getUserDisplay(1);
    expect(result).toBe("Alice (ID: 1)");
    expect(fetchUser).toHaveBeenCalledWith(1);
  });
});
Enter fullscreen mode Exit fullscreen mode

Snapshot Testing

import { it, expect } from "vitest";

function generateConfig(env: string) {
  return {
    database: env === "prod" ? "postgres" : "sqlite",
    cache: env === "prod" ? "redis" : "memory",
    debug: env !== "prod",
  };
}

it("generates correct config for production", () => {
  expect(generateConfig("prod")).toMatchInlineSnapshot(`
    {
      "cache": "redis",
      "database": "postgres",
      "debug": false,
    }
  `);
});
Enter fullscreen mode Exit fullscreen mode

In-Source Testing

One of Vitest unique features — write tests next to your code:

// math.ts
export function factorial(n: number): number {
  if (n < 0) throw new Error("Negative numbers not supported");
  if (n <= 1) return 1;
  return n * factorial(n - 1);
}

// Tests are tree-shaken from production builds
if (import.meta.vitest) {
  const { it, expect } = import.meta.vitest;

  it("computes factorial", () => {
    expect(factorial(0)).toBe(1);
    expect(factorial(5)).toBe(120);
    expect(factorial(10)).toBe(3628800);
  });

  it("throws on negative input", () => {
    expect(() => factorial(-1)).toThrow();
  });
}
Enter fullscreen mode Exit fullscreen mode

Speed Comparison

Framework 1000 Tests Watch Restart Config Complexity
Jest 8.2s 3.1s Medium
Mocha 6.5s 2.8s High
Vitest 1.4s 0.3s Low

Migration From Jest

Most Jest tests work with Vitest by just changing imports:

- import { describe, it, expect, jest } from "@jest/globals";
+ import { describe, it, expect, vi } from "vitest";

- jest.fn()
+ vi.fn()

- jest.mock("./module")
+ vi.mock("./module")
Enter fullscreen mode Exit fullscreen mode

The Bottom Line

Vitest is the testing framework that Jest should have been. It is faster, simpler to configure, and works natively with TypeScript and modern JavaScript tooling. If you are starting a new project or tired of slow Jest runs, give Vitest a try.

Start here: vitest.dev


💡 Need web scraping or data extraction? Check out my Apify actors or email me at spinov001@gmail.com for custom solutions!

Top comments (0)