Skip to main content

Test Framework

Hitler uses Vitest for testing. Vitest is fast, compatible with Jest APIs, and works great with TypeScript.

Running Tests

# Run all tests once
pnpm test

# Watch mode (re-runs on file changes)
pnpm test:watch

# With coverage report
pnpm test:coverage

Test Structure

/tests
  setup.ts                    # Global test setup
  /utils
    factories.ts              # Test data factories
    mock-database.ts          # Mock Drizzle ORM
    test-helpers.ts           # Utility functions
  /unit
    pattern-detection.test.ts # Pure function tests
    rules.test.ts             # Business rules tests
    sdk-client.test.ts        # SDK tests
    shared-schemas.test.ts    # Zod schema tests
    eod-parser.test.ts        # EOD emoji/status parsing tests
  /integration
    tasks.service.test.ts     # Service tests

/packages/*/src/**/*.test.ts  # Package-specific tests

Writing Tests

Unit Tests

For pure functions without side effects:
import { describe, it, expect } from "vitest";
import { calculateScore } from "@hitler/rules";

describe("calculateScore", () => {
  it("should return 100 for perfect metrics", () => {
    const result = calculateScore({
      tasksCompleted: 10,
      tasksTotal: 10,
      moodAverage: 5,
    });
    expect(result).toBe(100);
  });

  it("should return 0 when all tasks are overdue", () => {
    const result = calculateScore({
      tasksCompleted: 0,
      tasksTotal: 10,
      overdueCount: 10,
    });
    expect(result).toBe(0);
  });
});

Using Test Factories

Create consistent test data with factories:
import { createTask, createUser, createMood } from "../utils/factories";
import { daysAgo, daysFromNow } from "../utils/test-helpers";

describe("TaskService", () => {
  it("should identify overdue tasks", () => {
    const overdueTask = createTask({
      status: "pending",
      dueDate: daysAgo(3), // Due 3 days ago
    });

    const futureTask = createTask({
      status: "pending",
      dueDate: daysFromNow(5), // Due in 5 days
    });

    expect(isOverdue(overdueTask)).toBe(true);
    expect(isOverdue(futureTask)).toBe(false);
  });
});

Testing with Mocks

For tests that need external dependencies:
import { describe, it, expect, vi, beforeEach } from "vitest";

// Mock the module
vi.mock("@hitler/observability", () => ({
  logger: {
    info: vi.fn(),
    error: vi.fn(),
  },
}));

describe("NotificationService", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it("should log when notification is sent", async () => {
    const { logger } = await import("@hitler/observability");

    await sendNotification({ userId: "123", message: "Hello" });

    expect(logger.info).toHaveBeenCalledWith(
      expect.stringContaining("notification"),
      expect.any(Object)
    );
  });
});

Integration Tests

For testing services with mocked database:
import { describe, it, expect, beforeEach } from "vitest";
import { createMockDatabase, MockDatabase } from "../utils/mock-database";
import { TasksService } from "@hitler/api/modules/tasks";

describe("TasksService Integration", () => {
  let db: MockDatabase;
  let service: TasksService;

  beforeEach(() => {
    db = createMockDatabase();
    service = new TasksService(db);
  });

  it("should create task draft", async () => {
    const draft = await service.createDraft({
      title: "Test Task",
      userId: "user-1",
      organizationId: "org-1",
      priority: 3,
    });

    expect(draft.id).toBeDefined();
    expect(draft.title).toBe("Test Task");
    expect(draft.status).toBe("pending");
  });

  it("should not confirm another user's draft", async () => {
    const draft = await service.createDraft({
      title: "Test",
      userId: "user-1",
      organizationId: "org-1",
      priority: 3,
    });

    await expect(
      service.confirmDraft(draft.id, "user-2") // Different user
    ).rejects.toThrow("Cannot confirm another user's draft");
  });
});

Test Utilities

Factories

Located in tests/utils/factories.ts:
// Create a task with optional overrides
createTask({ status: "completed", priority: 5 });

// Create a user with role
createUser({ role: "manager" });

// Create a mood entry
createMood({ value: 4, note: "Good day!" });

// Create an organization
createOrganization({ name: "Acme Corp" });

Helpers

Located in tests/utils/test-helpers.ts:
// Date helpers
daysAgo(5); // Date 5 days in the past
daysFromNow(3); // Date 3 days in the future
hoursAgo(2); // Date 2 hours ago

// ID generators
generateId(); // Random UUID
generateSlug(); // Random slug like "acme-corp-a1b2"

Coverage Targets

PackageTargetWhy
@hitler/rules95%+Pure business logic, critical
@hitler/shared95%+Validation schemas, foundational
@hitler/sdk75%+API client, external dependency
@hitler/api70%+Service logic with mocked DB
Check coverage with:
pnpm test:coverage

Best Practices

Each test should be able to run in isolation. Use beforeEach to reset state.
beforeEach(() => {
  vi.clearAllMocks();
  db = createMockDatabase();
});
Focus on what the function does, not how it does it.
// Good - tests behavior
expect(task.isOverdue()).toBe(true);

// Bad - tests implementation
expect(task._checkDueDate()).toHaveBeenCalled();
Test names should describe the scenario and expected outcome.
// Good
it("should mark task as overdue when due date is in the past")

// Bad
it("test overdue")
Don’t test that Zod validates correctly. Test your schemas work as expected.
// Good - tests your schema
expect(() => taskSchema.parse({ title: "" })).toThrow();

// Bad - tests Zod itself
expect(z.string().min(1).parse("")).toThrow();

Debugging Tests

Run a specific test file

pnpm test tests/unit/rules.test.ts

Run tests matching a pattern

pnpm test -t "overdue"

Verbose output

pnpm test -- --reporter=verbose

Debug in VS Code

Add this to .vscode/launch.json:
{
  "type": "node",
  "request": "launch",
  "name": "Debug Tests",
  "program": "${workspaceFolder}/node_modules/vitest/vitest.mjs",
  "args": ["run", "--reporter=verbose"],
  "console": "integratedTerminal"
}