Skip to content

Testing

PrintStudio uses Bun’s built-in test runner for unit tests and Vitest for integration tests that need the full infrastructure (Postgres, Redis).

Terminal window
# Run all tests
bun run test
# Run tests in watch mode
bun run test:watch
# Run tests for a specific package
cd packages/core && bun test
# Run a specific test file
bun test packages/core/src/pricing/pricing.test.ts
# Run with coverage
bun test --coverage

Integration tests that need a real database and Redis use a dedicated Docker test environment defined in scripts/test-infra.ts:

Terminal window
# Start test infrastructure (separate from dev infra)
bun run test:infra:up
# Run integration tests
bun run test
# Stop test infrastructure
bun run test:infra:down
# Check status
bun run test:infra:status

The test infrastructure uses isolated Docker containers with a fresh database per test run.

Set TEST_INFRA_SKIP=true to run only tests that don’t need Postgres/Redis:

Terminal window
TEST_INFRA_SKIP=true bun run test

This is used in CI when running unit tests in the quality job without starting Docker.

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.ts

Unit tests live next to source files. Integration tests that span multiple packages live in packages/test-suite/.

packages/core/src/pricing/pricing.test.ts
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)”
packages/test-suite/src/api/orders.test.ts
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);
});

The CI pipeline (see CI/CD) runs tests in three stages:

JobTestsInfrastructure
qualityUnit tests, linting, type checkingNone (TEST_INFRA_SKIP=true)
core-db-testsDatabase integration testsPostgres + Redis via Docker
test-suiteEnd-to-end API testsFull stack

Coverage is collected with @vitest/coverage-v8:

Terminal window
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.

Use the seed script to populate test data:

Terminal window
bun run db:seed

Or create test fixtures programmatically using the helpers in packages/test-suite/src/fixtures/.