# How to Write Unit and Integration Tests for Kaneo

> Learn how to write unit and integration tests for Kaneo using Vitest. Discover best practices for testing API logic and full-stack validation against PostgreSQL.

- Repository: [kaneo.app/kaneo](https://github.com/usekaneo/kaneo)
- Tags: how-to-guide
- Published: 2026-08-06

---

**Kaneo uses Vitest for both unit and integration tests, with unit tests located in `tests/api/**/*.test.ts` for isolated logic and integration tests in `tests/api-integration/**/*.test.ts` for full-stack API validation against a real PostgreSQL database.**

Kaneo is an open-source project management platform built as a modern monorepo, and its testing strategy relies entirely on Vitest to verify both isolated utilities and full HTTP request pipelines. Whether you are adding a new validator or a new route, knowing how to write unit and integration tests for Kaneo ensures your changes stay reliable across the entire stack. The repository separates these concerns into two distinct directories with dedicated Vitest configurations.

## Kaneo Test Suite Architecture

Kaneo organizes its tests into two categories:

| Test type | Location | Purpose |
|-----------|----------|---------|
| **Unit tests** | `tests/api/**/*.test.ts` | Validate isolated functions, validators, helpers, and small modules without touching the database or HTTP layer. |
| **Integration tests** | `tests/api-integration/**/*.test.ts` | Spin up the full API server, hit real HTTP endpoints, and verify end‑to‑end behavior against a real PostgreSQL test database. |

## Unit Test Configuration

In [`apps/api/vitest.config.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/vitest.config.ts), the project configures Vitest to run in a Node environment and to match files under `tests/api/**/*.test.ts`. This setup is optimized for speed and isolation, running each module independently without external services.

## Writing Unit Tests for Kaneo

Unit tests import the target function directly from its source module and use Vitest's `describe`, `it`, and `expect` APIs. This pattern is ideal for verifying pure logic such as the date validators defined in [`apps/api/src/utils/validate-dates.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/utils/validate-dates.ts).

### Example: Testing Date Utilities

The following test from [`tests/api/utils/validate-dates.test.ts`](https://github.com/usekaneo/kaneo/blob/main/tests/api/utils/validate-dates.test.ts) exercises `validateAndParseDate` and `validateDateRange`:

```typescript
import { describe, expect, it } from "vitest";
import {
  validateAndParseDate,
  validateDateRange,
} from "@/utils/validate-dates";

describe("validateAndParseDate", () => {
  it("parses a valid ISO string", () => {
    const d = validateAndParseDate("2026-08-01T00:00:00.000Z", "dueDate");
    expect(d).toBeInstanceOf(Date);
    expect(d.toISOString()).toBe("2026-08-01T00:00:00.000Z");
  });

  it("rejects empty strings", () => {
    expect(() => validateAndParseDate("", "startDate"))
      .toThrowError(/startDate cannot be an empty string/);
  });
});

describe("validateDateRange", () => {
  it("fails when startDate is after dueDate", () => {
    const start = new Date("2026-09-01");
    const due = new Date("2026-08-01");
    expect(() => validateDateRange(start, due))
      .toThrowError(/Start date cannot be after due date/);
  });
});

```

When writing new unit tests, import the code under test from its source path, assert success paths with `expect`, and confirm error branches with `toThrowError`.

## Integration Test Configuration

Integration testing uses a separate Vitest configuration at [`apps/api/vitest.integration.config.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/vitest.integration.config.ts). This file points to [`tests/api-integration/setup.ts`](https://github.com/usekaneo/kaneo/blob/main/tests/api-integration/setup.ts) as a global setup file, prepares a test-only PostgreSQL URL, and disables parallel workers to keep database state predictable.

The setup file [`tests/api-integration/setup.ts`](https://github.com/usekaneo/kaneo/blob/main/tests/api-integration/setup.ts) configures environment variables, overrides the local `.env` loader, and ensures all integration tests connect to a dedicated `_test` PostgreSQL database.

## Writing Integration Tests for Kaneo

Integration tests exercise the complete Hono request pipeline. The pattern involves five repeatable steps:

1. **Reset the database** – Call `resetTestDatabase` from [`tests/api-integration/helpers/database.ts`](https://github.com/usekaneo/kaneo/blob/main/tests/api-integration/helpers/database.ts) to truncate tables and re-run migrations before each test.
2. **Mock authentication** – Use `mockAuthenticatedSession` or `mockAnonymousSession` from [`tests/api-integration/helpers/auth.ts`](https://github.com/usekaneo/kaneo/blob/main/tests/api-integration/helpers/auth.ts) to set the Hono context's session data.
3. **Create the app instance** – Import `createApp` from [`apps/api/src/index.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/index.ts) to build the full Hono server with all routes and middleware.
4. **Send HTTP requests** – Use Hono's built-in `app.request(path, options)` helper to invoke endpoints without starting an actual network server.
5. **Assert on responses and database state** – Check JSON payloads and query Drizzle ORM to confirm persistence.

### Example: Task Creation Integration Test

The following pattern from [`tests/api-integration/task.test.ts`](https://github.com/usekaneo/kaneo/blob/main/tests/api-integration/task.test.ts) demonstrates creating a task through the API and verifying both the response and the database row:

```typescript
import { describe, expect, it, beforeEach } from "vitest";
import db, { schema } from "@/database";
import { createApp } from "@/index";
import { mockAuthenticatedSession } from "./helpers/auth";
import { resetTestDatabase } from "./helpers/database";
import { createWorkspaceMember, createProjectFixture } from "./helpers/fixtures";

describe("API integration: task creation", () => {
  beforeEach(async () => {
    await resetTestDatabase();
  });

  it("creates a task with optional start/due dates", async () => {
    const member = await createWorkspaceMember();
    const { project, columns } = await createProjectFixture({
      workspaceId: member.workspace.id,
    });

    mockAuthenticatedSession(member.user);
    const { app } = createApp();

    const resp = await app.request(`/api/task/${project.id}`, {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({
        title: "Plan release",
        description: "Include start/due dates",
        priority: "medium",
        status: "in-progress",
        startDate: "2026-04-01T09:00:00.000Z",
        dueDate: "2026-04-05T17:00:00.000Z",
      }),
    });

    expect(resp.status).toBe(200);
    const payload = await resp.json();

    expect(payload).toMatchObject({
      projectId: project.id,
      title: "Plan release",
      startDate: "2026-04-01T09:00:00.000Z",
      dueDate: "2026-04-05T17:00:00.000Z",
    });

    const persisted = await db.query.taskTable.findFirst({
      where: { id: payload.id },
    });
    expect(persisted?.startDate?.toISOString()).toBe(
      "2026-04-01T09:00:00.000Z",
    );
    expect(persisted?.dueDate?.toISOString()).toBe(
      "2026-04-05T17:00:00.000Z",
    );
  });
});

```

This approach validates the entire stack, from HTTP routing and middleware to database persistence via Drizzle ORM.

## Running the Kaneo Test Suite

Kaneo exposes two Turbo-wrapped commands that automatically lint and type-check before executing Vitest:

- **Unit tests:** `pnpm test` runs Vitest with [`apps/api/vitest.config.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/vitest.config.ts).
- **Integration tests:** `pnpm test:integration` runs Vitest with [`apps/api/vitest.integration.config.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/vitest.integration.config.ts).

Both commands respect the monorepo layout and ensure test code stays consistent with the rest of the codebase.

## Summary

- **Unit tests** in `tests/api/**/*.test.ts` use [`apps/api/vitest.config.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/vitest.config.ts) to verify isolated logic without external services.
- **Integration tests** in `tests/api-integration/**/*.test.ts` use [`apps/api/vitest.integration.config.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/vitest.integration.config.ts) to exercise the full Hono API against a live PostgreSQL `_test` database.
- **Database helpers** in [`tests/api-integration/helpers/database.ts`](https://github.com/usekaneo/kaneo/blob/main/tests/api-integration/helpers/database.ts) reset state between tests, while **auth helpers** in [`tests/api-integration/helpers/auth.ts`](https://github.com/usekaneo/kaneo/blob/main/tests/api-integration/helpers/auth.ts) mock session data.
- **Hono's `app.request`** enables real HTTP endpoint testing without a network server.
- Execute unit tests with `pnpm test` and integration tests with `pnpm test:integration`.

## Frequently Asked Questions

### Where should I place new unit tests in the Kaneo codebase?

Place new unit tests under `tests/api/` following the `**/*.test.ts` pattern. For example, a utility located at [`apps/api/src/utils/validate-dates.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/src/utils/validate-dates.ts) should have a corresponding test at [`tests/api/utils/validate-dates.test.ts`](https://github.com/usekaneo/kaneo/blob/main/tests/api/utils/validate-dates.test.ts). Vitest discovers these files through the `include` pattern defined in [`apps/api/vitest.config.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/vitest.config.ts).

### How does Kaneo prevent test pollution in integration tests?

The integration suite calls `resetTestDatabase` from [`tests/api-integration/helpers/database.ts`](https://github.com/usekaneo/kaneo/blob/main/tests/api-integration/helpers/database.ts) inside a `beforeEach` hook to truncate tables and re-run migrations before every test. Additionally, [`apps/api/vitest.integration.config.ts`](https://github.com/usekaneo/kaneo/blob/main/apps/api/vitest.integration.config.ts) disables parallel workers, forcing sequential execution so tests cannot collide over shared PostgreSQL state.

### Can I run Kaneo integration tests without PostgreSQL?

No. As implemented in `usekaneo/kaneo`, integration tests require a live PostgreSQL instance. The setup file [`tests/api-integration/setup.ts`](https://github.com/usekaneo/kaneo/blob/main/tests/api-integration/setup.ts) forces a test-only database URL ending in `_test` and disables the local `.env` loader to ensure isolation, but the database server must be reachable for the suite to pass.

### What do the auth helpers in Kaneo integration tests do?

[`tests/api-integration/helpers/auth.ts`](https://github.com/usekaneo/kaneo/blob/main/tests/api-integration/helpers/auth.ts) provides `mockAuthenticatedSession` and `mockAnonymousSession` to inject session data directly into the Hono context. Use `mockAuthenticatedSession` when testing protected routes that require a logged-in user, and `mockAnonymousSession` for flows that expect an unauthenticated identity.