Testing & Debugging
Unit Testing
Unit testing in Bascik focuses on verifying pure functions, utility modules, data transformers, and business logic in isolation. Because Node 24 and Node 22.18+ natively execute TypeScript files by erasing type annotations, Vitest executes unit tests directly against .ts files with zero build delay.
Setting Up Vitest
If your project was scaffolded with create-bascik, Vitest is pre-configured. To set up Vitest manually in an existing project, install the dependencies:
npm install -D vitest @vitest/coverage-v8 Create a vite.config.js file in your project root:
import { defineConfig } from "vite";
export default defineConfig({
test: {
include: ["src/**/*.test.ts"],
coverage: {
provider: "v8",
reporter: ["text", "json", "html"],
},
},
}); Add test commands to your package.json:
"scripts": {
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage"
} Writing High-Value Unit Tests
Focus unit tests on core business logic, algorithm correctness, and edge-case handling rather than trivial property checks.
Example: Testing Pure Calculation Functions
// src/utils/formatters.ts
export function formatCurrency(cents: number, currency = "USD"): string {
if (isNaN(cents) || cents < 0) return "$0.00";
return new Intl.NumberFormat("en-US", {
style: "currency",
currency,
}).format(cents / 100);
} // src/utils/formatters.test.ts
import { describe, it, expect } from "vitest";
import { formatCurrency } from "./formatters.ts";
describe("formatCurrency", () => {
it("formats positive cent values correctly", () => {
expect(formatCurrency(1999)).toBe("$19.99");
expect(formatCurrency(500)).toBe("$5.00");
});
it("handles zero and invalid inputs gracefully", () => {
expect(formatCurrency(0)).toBe("$0.00");
expect(formatCurrency(-500)).toBe("$0.00");
expect(formatCurrency(NaN)).toBe("$0.00");
});
}); Testing API Route Handlers
Because Bascik API routes use the web standard Request and Response contract, you can unit test handlers directly by passing a Request object without spinning up a live network server or using heavy mock libraries:
// src/api/contact.test.ts
import { describe, it, expect } from "vitest";
import { POST } from "./contact.ts";
describe("POST /api/contact", () => {
it("creates contact message successfully", async () => {
const request = new Request("http://localhost/api/contact", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ name: "Alice", email: "alice@example.com" }),
});
const response = await POST(request, { params: {}, remoteIp: "127.0.0.1" });
expect(response.status).toBe(201);
const data = await response.json();
expect(data.ok).toBe(true);
});
}); Property-Based Testing with fast-check
For complex parsers, string token replacers, and mathematical transforms, use property-based testing (fast-check) to generate thousands of randomized inputs and verify system invariants across all edge cases:
npm install -D fast-check // src/utils/slugify.test.ts
import { describe, it, expect } from "vitest";
import fc from "fast-check";
function slugify(input: string): string {
return input
.toLowerCase()
.trim()
.replace(/[^\w\s-]/g, "")
.replace(/[\s_-]+/g, "-")
.replace(/^-+|-+$/g, "");
}
describe("slugify property invariants", () => {
it("never outputs spaces or uppercase characters", () => {
fc.assert(
fc.property(fc.string(), (str) => {
const slug = slugify(str);
expect(slug).not.toMatch(/\s/);
expect(slug).toBe(slug.toLowerCase());
}),
);
});
}); V8 Code Coverage Reports
Running npm run test:coverage generates comprehensive code coverage metrics powered by @vitest/coverage-v8:
- Terminal Summary: Displays statement, branch, function, and line coverage percentages directly in stdout.
- Interactive HTML Report: Generated in
coverage/index.htmlfor line-by-line inspection of untested paths in any browser. - CI Artifacts: Saved in
coverage/coverage-final.jsonfor integration into automated CI/CD pipelines.
Tip: Add coverage/ to your .gitignore file to prevent committing generated coverage artifacts to version control.