How to Write Unit and Integration Tests for Kaneo
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, 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.
Example: Testing Date Utilities
The following test from tests/api/utils/validate-dates.test.ts exercises validateAndParseDate and validateDateRange:
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. This file points to 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 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:
- Reset the database – Call
resetTestDatabasefromtests/api-integration/helpers/database.tsto truncate tables and re-run migrations before each test. - Mock authentication – Use
mockAuthenticatedSessionormockAnonymousSessionfromtests/api-integration/helpers/auth.tsto set the Hono context's session data. - Create the app instance – Import
createAppfromapps/api/src/index.tsto build the full Hono server with all routes and middleware. - Send HTTP requests – Use Hono's built-in
app.request(path, options)helper to invoke endpoints without starting an actual network server. - 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 demonstrates creating a task through the API and verifying both the response and the database row:
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 testruns Vitest withapps/api/vitest.config.ts. - Integration tests:
pnpm test:integrationruns Vitest withapps/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.tsuseapps/api/vitest.config.tsto verify isolated logic without external services. - Integration tests in
tests/api-integration/**/*.test.tsuseapps/api/vitest.integration.config.tsto exercise the full Hono API against a live PostgreSQL_testdatabase. - Database helpers in
tests/api-integration/helpers/database.tsreset state between tests, while auth helpers intests/api-integration/helpers/auth.tsmock session data. - Hono's
app.requestenables real HTTP endpoint testing without a network server. - Execute unit tests with
pnpm testand integration tests withpnpm 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 should have a corresponding test at tests/api/utils/validate-dates.test.ts. Vitest discovers these files through the include pattern defined in 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 inside a beforeEach hook to truncate tables and re-run migrations before every test. Additionally, 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 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 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →