zudo-test-wisdom

Type to search...

to open search from anywhere

Vitest Patterns

Unit tests, component tests, and build output tests with Vitest.

Workspace-Level Vitest Configs

Large projects often need different Vitest configurations for different test types. Use Vitest workspaces to manage this:

// vitest.workspace.ts
import { defineWorkspace } from "vitest/config";

export default defineWorkspace([
  {
    test: {
      name: "unit",
      include: ["src/**/*.test.ts"],
      environment: "node",
    },
  },
  {
    test: {
      name: "component",
      include: ["src/**/*.test.tsx"],
      environment: "jsdom",
    },
  },
  {
    test: {
      name: "build",
      include: ["tests/build/**/*.test.ts"],
      environment: "node",
    },
  },
]);

💡 Tip

Separate configs let you run fast unit tests independently from slower component or build tests: vitest --project unit vs vitest --project component.

jsdom and happy-dom Environments

Choose the right DOM environment for component tests:

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

export default defineConfig({
  test: {
    environment: "jsdom",
    // Or use happy-dom for faster execution:
    // environment: "happy-dom",
    globals: true,
    setupFiles: ["./test-setup.ts"],
  },
});

Per-file environment override when needed:

// @vitest-environment jsdom
import { describe, it, expect } from "vitest";

describe("DOM-dependent test", () => {
  it("manipulates the document", () => {
    document.body.innerHTML = '<div id="app">Hello</div>';
    expect(document.getElementById("app")?.textContent).toBe("Hello");
  });
});

Separate Configs for Different Test Types

A pattern from mdx-formatter: separate configurations for unit tests, API tests, and function tests:

vitest.config.ts          # Default: unit tests
vitest.config.api.ts      # API integration tests
vitest.config.functions.ts # Cloud function tests
{
  "scripts": {
    "test": "vitest",
    "test:api": "vitest --config vitest.config.api.ts",
    "test:functions": "vitest --config vitest.config.functions.ts",
    "test:all": "vitest && vitest --config vitest.config.api.ts"
  }
}

Contract Testing: Rust Engine via Vitest

From mdx-formatter: the Vitest suite serves as a contract test for the Rust formatting engine. The Node.js wrapper calls the Rust binary, and Vitest verifies the output matches expectations:

// tests/contract.test.ts
import { describe, it, expect } from "vitest";
import { execSync } from "child_process";

describe("Rust formatter contract", () => {
  it("formats basic MDX correctly", () => {
    const input = "# Hello\nSome   text   here";
    const result = execSync(`echo '${input}' | ./target/release/formatter`, {
      encoding: "utf-8",
    });
    expect(result.trim()).toBe("# Hello\n\nSome text here");
  });
});

📝 Note

Contract testing lets you verify a binary’s behavior from a higher-level language. The Vitest suite acts as the specification — if the Rust engine changes behavior, the contract tests catch it.

Idempotency Testing

A powerful invariant for formatters and transformers: applying the operation twice should produce the same result as applying it once.

// tests/idempotency.test.ts
import { describe, it, expect } from "vitest";
import { format } from "../src/format";
import { readFileSync, readdirSync } from "fs";
import { join } from "path";

const FIXTURES_DIR = join(__dirname, "fixtures");

describe("idempotency", () => {
  const fixtures = readdirSync(FIXTURES_DIR).filter((f) =>
    f.endsWith(".mdx")
  );

  for (const fixture of fixtures) {
    it(`is idempotent for ${fixture}`, () => {
      const input = readFileSync(join(FIXTURES_DIR, fixture), "utf-8");
      const firstPass = format(input);
      const secondPass = format(firstPass);
      expect(firstPass).toBe(secondPass);
    });
  }
});

Miniflare + D1/R2 Integration Tests

From zudo-pattern-gen: testing Cloudflare Workers with local D1 database and R2 storage using Miniflare:

// tests/integration.test.ts
import { describe, it, expect, beforeAll } from "vitest";
import { Miniflare } from "miniflare";

describe("Worker with D1", () => {
  let mf: Miniflare;

  beforeAll(async () => {
    mf = new Miniflare({
      modules: true,
      script: `export default { async fetch(req, env) { /* ... */ } }`,
      d1Databases: ["DB"],
      r2Buckets: ["STORAGE"],
    });

    // Run migrations
    const db = await mf.getD1Database("DB");
    await db.exec(`
      CREATE TABLE IF NOT EXISTS patterns (
        id TEXT PRIMARY KEY,
        name TEXT NOT NULL,
        data TEXT NOT NULL
      )
    `);
  });

  it("stores and retrieves a pattern", async () => {
    const resp = await mf.dispatchFetch("http://localhost/api/patterns", {
      method: "POST",
      body: JSON.stringify({ name: "test", data: "{}" }),
    });
    expect(resp.status).toBe(201);

    const getResp = await mf.dispatchFetch("http://localhost/api/patterns");
    const patterns = await getResp.json();
    expect(patterns).toHaveLength(1);
    expect(patterns[0].name).toBe("test");
  });
});

💡 Tip

Miniflare runs the same Workers runtime locally, so integration tests closely match production behavior. Combined with D1 and R2 bindings, you can test full data flows without deploying.

Revision History