Skip to Content

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.

users.test.ts
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' });

On this page