AponiaJSDocs
Recipes

Testing

Unit-test services directly and exercise HTTP routes in process with Bun.

Unit-test a controller

import { describe, expect, test } from "bun:test";
import { GreetingController } from "./greeting.controller.ts";
import { GreetingService } from "./greeting.service.ts";

describe("GreetingController", () => {
  test("returns a greeting from the service", () => {
    const controller = new GreetingController(new GreetingService());

    expect(controller.getGreeting()).toBe("Hello, AponiaJS!");
  });
});

Direct construction keeps unit tests independent of the container.

Test the complete route

import { expect, test } from "bun:test";
import { AponiaFactory } from "@aponiajs/platform-elysia";
import { AppModule } from "../src/app.module.ts";

test("GET /greetings", async () => {
  const application = await AponiaFactory.create(AppModule, {
    logger: false,
  });

  const response = await application.handle(
    new Request("http://localhost/greetings"),
  );

  expect(response.status).toBe(200);
  expect(await response.text()).toBe("Hello, AponiaJS!");
});

Assert a validation failure

A schema rejects the request before the handler runs, so the test asserts the status rather than a message body:

import { expect, test } from "bun:test";
import { AponiaFactory } from "@aponiajs/platform-elysia";
import { AppModule } from "../src/app.module.ts";

test("POST /users rejects an invalid body", async () => {
  const application = await AponiaFactory.create(AppModule, {
    logger: false,
  });

  const rejected = await application.handle(
    new Request("http://localhost/users", {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ name: "A" }),
    }),
  );

  expect(rejected.status).toBe(422);
});

Test a typed native route

When the module graph declares routes statically, return the native application and pass it directly to Eden Treaty:

import { expect, test } from "bun:test";
import { treaty } from "@elysia/eden";
import { app } from "../src/server.ts";

test("reads a user through Treaty", async () => {
  const api = treaty(app);
  const result = await api.users({ id: 42 }).get();

  expect(result.error).toBeNull();
  expect(result.data).toEqual({ id: 42, name: "user-42" });
});

This performs no network I/O and keeps the route contract inferred from the same Elysia application. See Eden Treaty for the static inference boundary.

Assert a graph failure

Graph and container failures happen while the application is being created. Assert the code, never the message:

import { expect, test } from "bun:test";
import { Injectable, Module } from "@aponiajs/common";
import { AponiaFactory } from "@aponiajs/platform-elysia";

class MissingDependency {}

@Injectable()
class BrokenService {
  constructor(readonly dependency: MissingDependency) {}
}

@Module({ providers: [BrokenService] })
class BrokenModule {}

test("reports a stable graph error code", async () => {
  await expect(
    AponiaFactory.create(BrokenModule, { logger: false }),
  ).rejects.toMatchObject({
    code: "MISSING_PROVIDER",
  });
});

The complete list is in error codes.

Run tests with:

bun test

handle() never binds a port, so nothing needs to be closed. application.close() only stops an existing Elysia server, so call it after application.listen(port). There is no testing module or provider override API yet.

On this page