Skip to content

Testing

PXL uses Vitest for unit, integration, and (optionally) end-to-end tests. This guide outlines recommended patterns for fast, reliable coverage.

Test Categories

TypeScopeSpeedGoal
UnitPure functions / servicesVery fastBusiness logic correctness
IntegrationMultiple modules (webserver + db/redis)MediumWiring & side-effects
CLI / CommandCommand handlersMediumOperational tasks function
Performance (selective)Critical paths w/ metricsSlowerDetect regressions

Scripts (package.json)

  • test – all tests
  • test:unit – subset (e.g. test/unit)
  • test:integration – integration suite
  • test:coverage – generate V8 coverage report
  • test:ui – interactive UI (Vitest)

Directory Layout


  unit/
    user.service.spec.ts
  integration/
    webserver.spec.ts
    queue.spec.ts
  utils/
    app-factory.ts
  setup.ts
  vitest-setup.ts

Global Setup Files

  • test/setup.ts: runtime bootstrap for env vars, polyfills
  • test/vitest-setup.ts: registered in vitest.config.ts for hooks / custom matchers

Create a reusable factory for tests to avoid duplication:

ts
// test/utils/app-factory.ts
import { Application } from '@scpxl/nodejs-framework';

export async function createTestApp(overrides: any = {}) {
  const app = new Application({
    webserver: { port: 0 },
    logger: { level: 'error' },
    ...overrides,
  });
  await app.start();
  return app;
}

Usage:

ts
import { describe, it, expect } from 'vitest';
import { createTestApp } from '../utils/app-factory';

describe('webserver', () => {
  it('responds /health', async () => {
    const app = await createTestApp();
    app.webserver.route({ method: 'GET', url: '/health', handler: () => ({ ok: true }) });

    const res = await app.webserver.fastify.inject({ method: 'GET', url: '/health' });
    expect(res.statusCode).toBe(200);
    expect(JSON.parse(res.payload)).toEqual({ ok: true });
    await app.stop();
  });
});

Redis & Database Strategies

GoalStrategy
Fast iterationMock Redis client methods
Realistic integrationUse redis-memory-server
Full DB behaviorSpin ephemeral Postgres (Testcontainers / local)
Pure unit testsOmit database + redis modules

Example (skipping Redis):

ts
const app = await createTestApp({ redis: undefined, cache: { enabled: false } });

Queue Testing

For deterministic queue tests:

  1. Use an isolated queue prefix per test (queue: { enabled: true, prefix: 'test-' + Date.now() }).
  2. Await job completion by listening for completed event.
  3. Clean up by calling await app.stop() to close workers.

WebSocket Testing

Use an in-process WebSocket client (e.g. ws) and connect against fastify.server.address(). Ensure you await app.start() before connecting.

Performance Assertions (Optional)

Leverage PerformanceMonitor for threshold checks:

ts
const perf = app.performance; // if surfaced
// run operation
const report = perf.generateReport();
expect(report.summary.averages.http).toBeLessThan(250);

Gate only the most critical paths to avoid flaky tests.

Mocking Patterns

TargetApproach
External HTTPStub via nock / fetch mock
RedisReplace methods with in-memory maps
Queue jobsDirectly invoke processor function
Logger noiseSet level to error

Example Unit Service Test

ts
class UserService {
  constructor(private log: any) {}
  greet(name: string) {
    this.log.debug('greet', { name });
    return `Hello ${name}`;
  }
}

import { describe, it, expect, vi } from 'vitest';

describe('UserService', () => {
  it('greets user', () => {
    const logger = { debug: vi.fn() };
    const svc = new UserService(logger);
    expect(svc.greet('Dev')).toBe('Hello Dev');
  });
});

Coverage Recommendations

  • Focus on decision branches & failure modes (config missing, invalid input) rather than 100% line coverage.
  • Track mutation of config merges and lifecycle hooks.

CI Tips

ConcernRecommendation
Flaky startupAdd retry around ephemeral dependencies
Slow runsSplit unit / integration in parallel jobs
Resource leaksEnsure every test that creates Application calls await app.stop()

Troubleshooting

SymptomLikely CauseFix
Jest/Vitest open handles warningUnstopped Application / open timerEnsure app.stop() and track intervals
Intermittent queue test failuresShared queue namespaceUse unique prefixes per test
Port in useHard-coded port reusedUse port: 0 to get ephemeral port

Next


Future addition: test utilities package export (factory helpers, mock builders).

Released under the ISC License.