Skip to content
intermediate Phase 15 · TypeScript Testing & Tools

Vitest Modern Testing (Advanced)

Use Vitest for fast TypeScript testing with native ESM support.

45m
0 problems
Topic Progress 0%

Configuration and Speed

Configuration and Speed

Vitest reuses your Vite config, so aliases, plugins, and TypeScript transform settings apply to tests automatically:

// vitest.config.ts
import { defineConfig } from "vitest/config";

export default defineConfig({
  test: {
    environment: "node",       // or "jsdom" / "happy-dom"
    globals: true,
    restoreMocks: true,
  },
});

Tests run as native ESM in worker threads — no separate Babel/ts-jest pipeline — which is why watch mode and startup are fast. Vite resolve.alias covers path mappings for both app and tests, so tsconfig paths and test resolution cannot drift apart.

Mocking with vi

Mocking with vi

vi.mock is hoisted like Jest's, and its factory can reference vi.hoisted for values that must exist first:

const ids = vi.hoisted(() => ({ next: 1 }));
vi.mock("../src/db", () => ({
  insert: vi.fn(() => ({ id: ids.next++ })),
}));

import { insert } from "../src/db";
expect(insert({ a: 1 })).toEqual({ id: 1 });

Timers: vi.useFakeTimers() then vi.advanceTimersByTime(1000) drives debounce and interval logic without real waiting. vi.spyOn(obj, "method") keeps original behavior until you mockReturnValue, and expect(...).toHaveBeenCalled works because Vitest's assertion layer shares Jest-compatible matchers.

Pitfalls and Interview Angles

Pitfalls and Interview Angles

Specifier sensitivity. vi.mock("../src/db") must match how the test imports the module — same relative path, or mocking silently applies to a different resolved file.

Globals on versus off. With globals: false (the default) you import describe/it/expect from vitest; editor types then come from vitest/globals only when globals are enabled.

Coverage and type testing. coverage: { provider: "v8" } instruments without instrumenting transforms, and expectTypeOf<T>().toMatchTypeOf<U>() (or assertType) checks generics at compile time in the same file.

Interview: contrast Vitest with Jest architecturally — Vite's module graph and native ESM versus Jest's CommonJS-centric registry — and say which config keys must move when migrating.

Quiz

1. Why do Vitest tests pick up your path aliases without extra mapping?

Question 1 options

2. What does vi.hoisted solve?

Question 2 options

3. Which pair correctly drives fake time?

Question 3 options

Flashcards

Question

Vitest core advantage

Answer

Runs through Vite: native ESM, shared config/plugins/aliases, instant watch startup.

Question

vi.mock

Answer

Hoisted module mock keyed by import specifier — must match the test's import path.

Question

vi.hoisted

Answer

Declares values that a hoisted vi.mock factory can safely reference.

Question

Fake timers

Answer

vi.useFakeTimers() + vi.advanceTimersByTime(ms) to test debounce/intervals without waiting.

Question

expectTypeOf

Answer

Compile-time assertion helper that fails the build when generic types mismatch.

Revision Notes

Key Takeaways

  • 1. One Vite config serves app build and tests, so aliases cannot drift
  • 2. Mock paths must mirror import specifiers exactly
  • 3. Type assertions run in the same suite as runtime tests

Interview Tips

  • • Explain the architectural difference from Jest: Vite module graph vs Jest registry
  • • Walk a fake-timer test for a debounce function step by step

Cheat Sheet

Vitest reuses vite.config aliases; vi.mock hoisted (match specifier), vi.hoisted for shared vars; useFakeTimers + advanceTimersByTime; expectTypeOf for type tests.

Continue Learning