Skip to main content

Testing Overview

Testing is a first-class citizen in Carno.js. We provide utilities to make it easy to write unit and integration tests for your applications.

Testing with the Database​

When writing integration tests that involve the ORM, you need a clean database state for each test. The withDatabase helper simplifies this process by handling connection management, schema creation, and cleanup.

Basic Usage​

The withDatabase helper creates a temporary schema, runs your migrations (or provided SQL statements), and executes your test routine within an ORM session.

import { withDatabase } from '@carno.js/orm/testing';
import { User } from '../entities/User';

describe('UserRepository', () => {
it('should create a user', async () => {
await withDatabase(async ({ orm }) => {
const user = await User.create({
name: 'John Doe',
email: 'john@example.com'
});

expect(user.id).toBeDefined();
});
});
});

Providing SQL Statements​

You can provide an array of SQL statements to initialize the database schema manually if you don't want to rely on migrations.

await withDatabase(
[
'CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT, email TEXT)'
],
async ({ orm, executeSql }) => {
// Your test logic here
await executeSql("INSERT INTO users (name) VALUES ('Jane')");
}
);

Options​

withDatabase accepts an options object to customize the connection and behavior.

await withDatabase(
async ({ orm }) => { /* ... */ },
{
schema: 'test_schema', // Custom schema name (default: public)
entityFile: 'src/**/*.entity.ts', // Glob pattern to load entities
connection: {
host: 'localhost',
// ... overrides for ConnectionSettings
}
}
);

How it Works​

  1. Schema Isolation: For every call, it drops and recreates the specified schema (defaulting to public).
  2. Auto-Migrations: If migrationPath is found in your carno.config.ts (or provided in options), it automatically loads and executes the SQL migration files.
  3. Session Context: It wraps the execution in an ormSessionContext, so that Active Record methods and Repositories use the correct Orm instance.
  4. Cleanup: Since it recreates the schema every time, each test starts with a completely fresh database.

Testing Controllers​

Carno ships a test harness for controller and HTTP testing. It creates a Carno instance, registers controllers/services, and can start a real HTTP server so you can call routes using fetch.

Quick start with automatic cleanup​

Use withTestApp when you want a small integration test and automatic cleanup. It always calls close() even if your test throws.

import { withTestApp, Controller, Get } from '@carno.js/core';

@Controller('/health')
class HealthController {
@Get()
health() {
return { status: 'ok' };
}
}

await withTestApp(
async (harness) => {
const response = await harness.get('/health');
expect(response.status).toBe(200);
expect(await response.json()).toEqual({ status: 'ok' });
},
{
controllers: [HealthController],
listen: true
}
);

Manual lifecycle control​

Use createTestHarness when you need to keep the server open for multiple assertions or want to set up extra state before closing.

import { createTestHarness } from '@carno.js/core';

const harness = await createTestHarness({
controllers: [HealthController],
services: [],
listen: true
});

try {
const response = await harness.get('/health');
expect(response.status).toBe(200);
} finally {
await harness.close();
}

Harness API overview​

  • app: The Carno instance you can configure directly.
  • container: The internal DI container.
  • server and port: Defined only when listen is enabled.
  • resolve(token): Resolve a service from the container.
  • request, get, post, put, delete: HTTP helpers. post and put serialize JSON and set Content-Type: application/json.
  • close(): Stops the server and cleans up.

Options​

  • controllers: Controllers to register on the test app.
  • services: Services or tokens to register in the DI container.
  • config: CarnoConfig overrides for the test instance.
  • listen: true to start a server on a random port, or a number to bind a specific port.
  • port: Alternate way to set a specific port (used when listen is true).

Notes​

  • If you do not set listen, the server will not start and HTTP helpers (request, get, post, put, delete) will throw.
  • request accepts either a full URL or a path like /health.