Fragno

Testing

Testing Fragno fragments

Fragno provides comprehensive testing utilities to help you test your fragments without running a server. The createFragmentForTest function creates a test instance with type-safe route handling and response parsing.

Creating a Test Fragment

import { createFragmentForTest } from "@fragno-dev/core/test";
import { routes } from "./fragment-definition";

const fragment = createFragmentForTest(myFragmentDefinition, routes, {
  config: { apiKey: "test-key" },
  // Optional: override dependencies
  deps: { httpClient: mockHttpClient },
  // Optional: override services
  services: { getUserById: mockGetUserById },
});

Testing Route Handlers

The callRoute method executes routes by method and path, returning a typed discriminated union:

const response = await fragment.callRoute("POST", "/users/:id", {
  pathParams: { id: "123" },
  body: { name: "John" },
  query: { filter: "active" },
  headers: { authorization: "Bearer token" },
});

// Response is a discriminated union
assert(response.type === "json");
expect(response.status).toBe(200);
expect(response.data).toEqual({ id: "123", name: "John" });

The method and path parameters are strongly typed based on your fragment's routes, providing autocomplete and type checking.

Response Types

The callRoute method returns a discriminated union with four possible types:

JSON Response

assert(response.type === "json");
expect(response.status).toBe(200);
expect(response.headers).toBeInstanceOf(Headers);
expect(response.data).toEqual({ id: "123", name: "John" });

Streaming Response

assert(response.type === "jsonStream");
const items = [];
for await (const item of response.stream) {
  items.push(item);
}
expect(items).toEqual([...expected]);

Empty Response

assert(response.type === "empty");
expect(response.status).toBe(204);

Error Response

assert(response.type === "error");
expect(response.status).toBe(404);
expect(response.error).toEqual({
  message: "Not found",
  code: "NOT_FOUND",
});

Testing Database Fragments

For Fragments that use @fragno-dev/db, use createDatabaseFragmentForTest from @fragno-dev/test to automatically set up an in-memory database with migrations:

import { createDatabaseFragmentForTest } from "@fragno-dev/test";

const { fragment, test } = await createDatabaseFragmentForTest(
  myDatabaseFragmentDefinition,
  routes,
  {
    adapter: { type: "kysely-sqlite" },
    config: { apiKey: "test-key" },
    // Optional: migrate to specific schema version (defaults to latest)
    migrateToVersion: 2,
  },
);

// Access fragment methods as usual
await fragment.services.createUser({ name: "John" });

// Or test routes directly
const response = await fragment.callRoute("POST", "/users", {
  body: { name: "John" },
});

Cleanup

The test.cleanup() function closes database connections and deletes any database files created during testing. This is useful in afterAll hooks to clean up resources:

describe("User tests", () => {
  const { fragment, test } = await createDatabaseFragmentForTest(authFragmentDefinition, routes, {
    adapter: { type: "kysely-sqlite" },
  });

  afterAll(async () => {
    await test.cleanup();
  });

  it("test 1", async () => {
    const response = await fragment.callRoute("POST", "/users", {
      body: { name: "Alice" },
    });
    expect(response.type).toBe("json");
  });
});

Fragments tested in this way use an in-memory SQLite database with migrations automatically applied. This means that you can directly test the data layer of your Fragment.