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: '[email protected]' }),
});
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.