Testing
Test the whole app in process, without a socket.
app.handle() takes a Fetch Request and returns the Response the server would send. No
port is opened, so tests are fast and can run in parallel.
import { expect, test } from 'bun:test';
import { Suku } from '@sukujs/core';
import { UsersFeature } from './users.feature.js';
test('GET /users/:id', async () => {
const app = await Suku.create({ features: [UsersFeature] });
const res = await app.handle(new Request('http://test/users/1'));
expect(res.status).toBe(200);
expect(await res.json()).toEqual({ id: 1, name: 'Ada Lovelace' });
});No adapter is needed. Everything runs: middlewares, validation, error handlers and the request scope.
Replacing services
configureServices runs after the features are scanned and before the container is built.
const app = await Suku.create({
features: [OrdersFeature],
configureServices: (services) =>
services
.replace(singleton(PaymentGateway, FakeGateway))
.replace(singleton(Logger, SilentLogger)),
});replace removes every registration for the token and adds yours.
Requests with bodies
const res = await app.handle(
new Request('http://test/users', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ name: 'Grace' }),
}),
);
expect(res.status).toBe(201);Testing one route
Routes are values, so a test can mount just the one it cares about.
const app = await Suku.create({
routes: [findUser],
services: [singleton(UsersService, FakeUsers)],
});Testing a service alone
The container needs no HTTP.
import { ServiceCollection } from '@sukujs/di';
const provider = new ServiceCollection()
.addSingleton(Clock, FixedClock)
.addSingleton(InvoiceService)
.build({ validateOnBuild: true });
expect(provider.get(InvoiceService).dueDate()).toBe('2026-12-01');Checking registered routes
expect(app.routes).toContainEqual({ method: 'GET', path: '/users/:id', name: 'UsersRoutes.findOne' });