jest-vitest

SKILL.md

Jest & Vitest

JavaScript/TypeScript testing with Jest and Vitest.

Running Tests

# Jest
npx jest
npx jest --watch
npx jest --coverage
npx jest path/to/test.ts
npx jest -t "test name pattern"

# Vitest
npx vitest
npx vitest run          # Single run (no watch)
npx vitest --coverage
npx vitest path/to/test.ts

Test Structure

// Jest and Vitest share the same API
describe("Calculator", () => {
  let calc: Calculator;

  beforeEach(() => {
    calc = new Calculator();
  });

  afterEach(() => {
    calc.reset();
  });

  it("should add two numbers", () => {
    expect(calc.add(2, 3)).toBe(5);
  });

  it("should throw on division by zero", () => {
    expect(() => calc.divide(1, 0)).toThrow("Division by zero");
  });

  describe("negative numbers", () => {
    it("should handle negative addition", () => {
      expect(calc.add(-1, -2)).toBe(-3);
    });
  });
});

Common Matchers

// Equality
expect(value).toBe(exact);           // === strict
expect(value).toEqual(deepEqual);    // Deep equality
expect(value).toStrictEqual(strict); // Deep + type

// Truthiness
expect(value).toBeTruthy();
expect(value).toBeFalsy();
expect(value).toBeNull();
expect(value).toBeUndefined();
expect(value).toBeDefined();

// Numbers
expect(value).toBeGreaterThan(3);
expect(value).toBeLessThanOrEqual(10);
expect(value).toBeCloseTo(0.3, 5);

// Strings
expect(str).toMatch(/regex/);
expect(str).toContain("substring");

// Arrays/Objects
expect(arr).toContain(item);
expect(arr).toHaveLength(3);
expect(obj).toHaveProperty("key", "value");
expect(obj).toMatchObject({ name: "Alice" });

// Exceptions
expect(() => fn()).toThrow();
expect(() => fn()).toThrow("message");
expect(() => fn()).toThrow(ErrorClass);

Mocking

// Mock function
const mockFn = jest.fn();              // Jest
const mockFn = vi.fn();                // Vitest

mockFn.mockReturnValue(42);
mockFn.mockReturnValueOnce(1);
mockFn.mockResolvedValue({ data: [] });
mockFn.mockImplementation((x) => x * 2);

// Assertions
expect(mockFn).toHaveBeenCalled();
expect(mockFn).toHaveBeenCalledTimes(2);
expect(mockFn).toHaveBeenCalledWith("arg1", "arg2");

// Mock module
jest.mock("./database");               // Jest
vi.mock("./database");                 // Vitest

// Mock with implementation
jest.mock("./api", () => ({
  fetchUser: jest.fn().mockResolvedValue({ name: "Alice" }),
}));

// Vitest equivalent
vi.mock("./api", () => ({
  fetchUser: vi.fn().mockResolvedValue({ name: "Alice" }),
}));

// Spy on existing method
const spy = jest.spyOn(object, "method");   // Jest
const spy = vi.spyOn(object, "method");     // Vitest

// Mock timers
jest.useFakeTimers();  // Jest
vi.useFakeTimers();    // Vitest

jest.advanceTimersByTime(1000);  // Jest
vi.advanceTimersByTime(1000);    // Vitest

Async Testing

// Async/await
it("fetches users", async () => {
  const users = await fetchUsers();
  expect(users).toHaveLength(3);
});

// Resolved/rejected
it("resolves with data", async () => {
  await expect(fetchData()).resolves.toEqual({ ok: true });
});

it("rejects with error", async () => {
  await expect(fetchBad()).rejects.toThrow("Network error");
});

Snapshot Testing

// Create/update snapshot
it("renders correctly", () => {
  const tree = render(<Button label="Click" />);
  expect(tree).toMatchSnapshot();
});

// Inline snapshot
it("serializes", () => {
  expect(serialize(data)).toMatchInlineSnapshot(`"expected output"`);
});

// Update snapshots: jest --updateSnapshot / vitest --update

React Testing (Testing Library)

import { render, screen, fireEvent, waitFor } from "@testing-library/react";

it("renders and interacts", async () => {
  render(<LoginForm onSubmit={mockSubmit} />);

  // Query elements
  const input = screen.getByRole("textbox", { name: /email/i });
  const button = screen.getByRole("button", { name: /submit/i });

  // Interact
  fireEvent.change(input, { target: { value: "alice@test.com" } });
  fireEvent.click(button);

  // Assert
  await waitFor(() => {
    expect(mockSubmit).toHaveBeenCalledWith("alice@test.com");
  });

  expect(screen.getByText("Success")).toBeInTheDocument();
});

// userEvent (more realistic)
import userEvent from "@testing-library/user-event";

it("types and submits", async () => {
  const user = userEvent.setup();
  render(<Form />);

  await user.type(screen.getByRole("textbox"), "hello");
  await user.click(screen.getByRole("button"));
});

Coverage

# Jest
npx jest --coverage --coverageThreshold='{"global":{"branches":80,"functions":80,"lines":80}}'

# Vitest (vitest.config.ts)
# coverage: { provider: "v8", thresholds: { lines: 80 } }

Configuration

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

export default defineConfig({
  test: {
    globals: true,
    environment: "jsdom",
    setupFiles: ["./tests/setup.ts"],
    coverage: {
      provider: "v8",
      reporter: ["text", "html"],
      exclude: ["node_modules/", "tests/"],
    },
  },
});
// jest.config.js
module.exports = {
  preset: "ts-jest",
  testEnvironment: "jsdom",
  setupFilesAfterSetup: ["./tests/setup.ts"],
  moduleNameMapper: {
    "^@/(.*)$": "<rootDir>/src/$1",
  },
  coverageThreshold: {
    global: { branches: 80, functions: 80, lines: 80, statements: 80 },
  },
};

Reference

For advanced patterns, React testing recipes, and migration guides: references/patterns.md

Weekly Installs
3
GitHub Stars
2
First Seen
Feb 21, 2026
Installed on
opencode3
gemini-cli3
claude-code3
github-copilot3
codex3
amp3