Test TypeScript Code with Vitest

Set up Vitest to test TypeScript code with zero configuration. Write unit tests, run them in watch mode, and use built-in type testing to catch type-level bugs.

6 min read

Vitest is a test runner built on top of Vite. It runs your test files, reports which pass and which fail, and re-runs tests automatically when you change your code. Because it uses esbuild for transpilation, it understands TypeScript natively without extra plugins or configuration.

You write tests in files named with the .test.ts or .spec.ts pattern. Vitest finds them, transforms the TypeScript to JavaScript on the fly, and executes them. For choosing the right TypeScript executor for your dev workflow, see tsx vs ts-node for Running TypeScript.

Install and Write Your First Test

Install Vitest as a dev dependency:

bashbash
npm install --save-dev vitest

With Vitest installed, create the code you want to test. Write a simple utility function in a file called src/math.ts:

typescripttypescript
export function add(a: number, b: number): number {
  return a + b;
}

Now create a test file alongside it. The test and expect functions come directly from vitest. Each test calls the add function with different inputs and checks the result:

typescripttypescript
import { expect, test } from "vitest";
import { add } from "./math";
 
test("adds two positive numbers", () => {
  expect(add(2, 3)).toBe(5);
});
 
test("adds a positive and a negative number", () => {
  expect(add(10, -4)).toBe(6);
});

Add a test script to your package.json so you can run tests with a single command. This wires vitest into your npm workflow and lets every contributor run the same test suite:

jsonjson
{
  "scripts": {
    "test": "vitest"
  }
}

Now execute the tests from your terminal. Vitest scans your project for test files, transforms the TypeScript on the fly, and reports each individual test result with a clear pass or fail status next to the test name:

bashbash
npm test

Vitest prints this to your terminal after the run finishes. Each test file gets its own line, and each test case underneath it gets a green checkmark for a pass or a red mark for a failure:

texttext
✓ src/math.test.ts (2)
  ✓ adds two positive numbers
  ✓ adds a positive and a negative number
 
Test Files  1 passed (1)
     Tests  2 passed (2)
  Duration  105ms

Vitest found the test file, transformed the TypeScript, and ran both tests. No config file was needed.

Watch Mode

Run vitest without the run subcommand and it stays in watch mode, waiting for file changes:

bashbash
npx vitest

In watch mode, Vitest re-runs only the tests affected by your changes. If you edit src/math.ts, only math.test.ts runs again. This keeps the feedback loop under a second, even in projects with hundreds of test files.

Common Matchers

Vitest's expect API is compatible with Jest for most matchers. Here are the most useful ones grouped by category.

For equality and truthiness checks:

typescripttypescript
import { expect, test } from "vitest";
 
test("equality and truthiness", () => {
  expect(2 + 2).toBe(4);
  expect({ name: "Ada" }).toEqual({ name: "Ada" });
  expect("hello").toBeTruthy();
  expect(null).toBeNull();
});

Numbers need approximate matching due to floating point, and you can check ordering with matchers like toBeGreaterThan. Strings support substring checks with toContain:

typescripttypescript
test("numbers and strings", () => {
  expect(0.1 + 0.2).toBeCloseTo(0.3);
  expect(5).toBeGreaterThan(2);
  expect("TypeScript").toContain("Script");
});

Arrays can be checked for length and content, and you can verify that functions throw specific errors. Use toThrow to confirm both the error type and message:

typescripttypescript
test("arrays and errors", () => {
  expect([1, 2, 3]).toHaveLength(3);
  expect(() => {
    throw new Error("broken");
  }).toThrow("broken");
});

Each matcher checks one specific condition. If the condition is not met, Vitest prints the expected and received values so you can see exactly what went wrong.

Testing Async Code

Vitest handles async tests the same way for both promises and async/await patterns:

typescripttypescript
import { expect, test } from "vitest";
 
async function fetchUser(id: number): Promise<{ id: number; name: string }> {
  return { id, name: "Ada" };
}
 
test("resolves with a user using async/await", async () => {
  const user = await fetchUser(1);
  expect(user).toEqual({ id: 1, name: "Ada" });
});

The resolves matcher waits for the promise to settle before checking the value. The rejects matcher does the same for expected failures. This lets you test both success and error paths cleanly.

Grouping Tests with describe

Group related tests with describe blocks to keep your test output organized:

typescripttypescript
import { describe, expect, test } from "vitest";
 
describe("math utilities", () => {
  test("adds positive numbers", () => {
    expect(2 + 3).toBe(5);
  });
 
  test("handles zero", () => {
    expect(5 + 0).toBe(5);
  });
});

Describe blocks can be nested. Vitest prints the full path of each test in the output so you can tell exactly which group a failing test belongs to.

Typecheck Mode

Runtime tests verify behavior. Typecheck mode verifies types. When you run vitest with the --typecheck flag, it uses the TypeScript compiler to check your test files and reports type errors as test failures.

bashbash
npx vitest run --typecheck

Use a dedicated script in your package.json so you can typecheck with a short command. Having both runtime tests and type checks in your npm scripts keeps your workflow consistent:

jsonjson
{
  "scripts": {
    "test": "vitest",
    "test:typecheck": "vitest --typecheck"
  }
}

With both scripts in place, npm test runs runtime tests and npm run test:typecheck runs type assertions. Use both in CI for full coverage.

Typecheck mode opens the door to compile-time assertions. You can write tests that check whether a function returns the correct type, without ever executing the function. For a complete guide on those patterns, see Write Type Safe Unit Tests.

Rune AI

Rune AI

Key Insights

  • Install Vitest with npm install -D vitest and add a test script to package.json.
  • Vitest understands TypeScript natively via esbuild; no ts-jest or babel config needed.
  • Write tests with test() and expect(), using the same API as Jest for most matchers.
  • Use vitest --typecheck to run compile-time type assertions with expectTypeOf.
  • Watch mode re-runs only the tests affected by your changes for fast feedback.
RunePowered by Rune AI

Frequently Asked Questions

Does Vitest work without a vite.config.ts?

Yes. Vitest reads your vite.config.ts if one exists, but it works fine without it. You can create a vitest.config.ts for test-specific settings, or rely entirely on defaults.

Can Vitest replace Jest for TypeScript projects?

Yes. Vitest is API-compatible with Jest for most common matchers and patterns. It is faster, supports TypeScript natively, and does not require separate config for transforming .ts files.

What is the difference between vitest and vitest --typecheck?

vitest runs your test files and checks runtime behavior. vitest --typecheck only type-checks your test files without executing them, catching type errors that runtime tests would miss.

Conclusion

Vitest runs TypeScript tests out of the box with no configuration. Install it, write tests with import { expect, test } from 'vitest', and run them. Add --typecheck to catch type-level bugs that runtime tests cannot see.