Testing
PrintStudio uses Bun’s built-in test runner for unit tests and Vitest for integration tests that need the full infrastructure (Postgres, Redis).
Running Tests
Section titled “Running Tests”# Run all testsbun run test
# Run tests in watch modebun run test:watch
# Run tests for a specific packagecd packages/core && bun test
# Run a specific test filebun test packages/core/src/pricing/pricing.test.ts
# Run with coveragebun test --coverageTest Infrastructure
Section titled “Test Infrastructure”Integration tests that need a real database and Redis use a dedicated Docker test environment defined in scripts/test-infra.ts:
# Start test infrastructure (separate from dev infra)bun run test:infra:up
# Run integration testsbun run test
# Stop test infrastructurebun run test:infra:down
# Check statusbun run test:infra:statusThe test infrastructure uses isolated Docker containers with a fresh database per test run.
Skipping Infrastructure
Section titled “Skipping Infrastructure”Set TEST_INFRA_SKIP=true to run only tests that don’t need Postgres/Redis:
TEST_INFRA_SKIP=true bun run testThis is used in CI when running unit tests in the quality job without starting Docker.
Test Structure
Section titled “Test Structure”packages/ core/ src/ pricing/ pricing.ts pricing.test.ts # unit tests alongside source state-machine/ state-machine.ts state-machine.test.ts test-suite/ # End-to-end and integration tests src/ api/ orders.test.ts jobs.test.ts integrations/ moonraker.test.tsUnit tests live next to source files. Integration tests that span multiple packages live in packages/test-suite/.
Writing Tests
Section titled “Writing Tests”Unit Tests (Bun test runner)
Section titled “Unit Tests (Bun test runner)”import { describe, expect, test } from "bun:test";import { calculatePrice } from "./pricing.js";
describe("calculatePrice", () => { test("applies quality tier multiplier", () => { const price = calculatePrice({ basePrice: 10.00, qualityTier: "fine", quantity: 1, }); expect(price).toBeCloseTo(14.00); // 1.4× multiplier });
test("applies batch discount for quantity >= 5", () => { const price = calculatePrice({ basePrice: 10.00, qualityTier: "standard", quantity: 5, }); expect(price).toBeLessThan(50.00); // discount applied });});Integration Tests (Vitest + testcontainers)
Section titled “Integration Tests (Vitest + testcontainers)”import { describe, it, expect, beforeAll, afterAll } from "vitest";import { createTestDb } from "@printstudio/test-suite/helpers";
let db: ReturnType<typeof createTestDb>;
beforeAll(async () => { db = await createTestDb(); await db.migrate();});
afterAll(async () => { await db.teardown();});
it("creates an order with jobs", async () => { const order = await createOrder(db, { customerId: "test-customer", lineItems: [{ skuId: "test-sku", quantity: 2 }], });
expect(order.id).toBeDefined(); expect(order.jobs).toHaveLength(2);});CI Test Jobs
Section titled “CI Test Jobs”The CI pipeline (see CI/CD) runs tests in three stages:
| Job | Tests | Infrastructure |
|---|---|---|
quality | Unit tests, linting, type checking | None (TEST_INFRA_SKIP=true) |
core-db-tests | Database integration tests | Postgres + Redis via Docker |
test-suite | End-to-end API tests | Full stack |
Coverage
Section titled “Coverage”Coverage is collected with @vitest/coverage-v8:
cd packages/core && bun test --coverage# Coverage report in coverage/Coverage reports are available in the coverage/ directory at the monorepo root after running tests.
Test Data
Section titled “Test Data”Use the seed script to populate test data:
bun run db:seedOr create test fixtures programmatically using the helpers in packages/test-suite/src/fixtures/.