title: Testing sidebar_position: 8 tags:
- testing
- unit-tests
- integration-tests
Testing
Minima.js is built with testability in mind. This guide covers testing strategies for your Minima.js applications using popular testing frameworks.
Quick Reference
- Setup - Configure your testing environment
- Unit Testing - Test individual handlers and functions
- Integration Testing - Test full request/response cycles
- Testing Hooks - Test lifecycle hooks
- Testing Plugins - Test custom plugins
- Mocking - Mock dependencies and context
- Best Practices - Testing recommendations
Setup
With Bun
Bun includes a built-in test runner:
bash
bun testExample test file (app.test.ts):
ts
import { describe, test, expect } from "@jest/globals";
import { createApp } from "@minimajs/server/bun";
describe("App", () => {
test("GET /health returns 200", async () => {
const app = createApp();
app.get("/health", () => ({ status: "ok" }));
const response = await app.handle(new Request("http://localhost/health"));
expect(response.status).toBe(200);
const data = await response.json();
expect(data).toEqual({ status: "ok" });
});
});Tip: Use
createRequest()from@minimajs/server/mockfor cleaner test code, or use nativenew Request()for more control.
With Jest/Vitest
Install dependencies:
bash
npm install --save-dev vitest
# or
npm install --save-dev jest @types/jestConfigure (vitest.config.ts):
ts
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
globals: true,
environment: "node",
},
});Unit Testing
Test individual handlers in isolation.
Testing Route Handlers
ts
import { describe, test, expect } from "bun:test";
import { createApp } from "@minimajs/server/bun";
import { createRequest } from "@minimajs/server/mock";
import { params, body } from "@minimajs/server";
describe("User Routes", () => {
test("GET /users/:id returns user", async () => {
const app = createApp();
app.get("/users/:id", () => {
const id = params.get("id");
return { id, name: "Alice" };
});
const response = await app.handle(createRequest("/users/123"));
expect(response.status).toBe(200);
const data = await response.json();
expect(data).toEqual({ id: "123", name: "Alice" });
});
test("POST /users creates user", async () => {
const app = createApp();
app.post("/users", () => {
const userData = body();
return { id: "456", ...userData };
});
const response = await app.handle(
createRequest("/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "Bob" }),
})
);
expect(response.status).toBe(200);
const data = await response.json();
expect(data).toEqual({ id: "456", name: "Bob" });
});
});Alternative: You can also use native
new Request("http://localhost/path", options)if you need more control over the request object.
Testing with Query Parameters
ts
import { createRequest } from "@minimajs/server/mock";
test("GET /search with query params", async () => {
const app = createApp();
app.get("/search", () => {
const params = searchParams();
const query = params.get("q");
return { results: [], query };
});
const response = await app.handle(createRequest("/search?q=test"));
const data = await response.json();
expect(data.query).toBe("test");
});Testing Headers
ts
import { createRequest } from "@minimajs/server/mock";
test("requires authorization header", async () => {
const app = createApp();
app.get("/protected", () => {
const auth = headers().get("authorization");
if (!auth) {
response.status(401);
return { error: "Unauthorized" };
}
return { data: "secret" };
});
// Without auth
const res1 = await app.handle(createRequest("/protected"));
expect(res1.status).toBe(401);
// With auth
const res2 = await app.handle(
createRequest("/protected", {
headers: { Authorization: "Bearer token" },
})
);
expect(res2.status).toBe(200);
});Integration Testing
Test the full application with plugins and middleware.
Testing with Plugins
ts
import { createRequest } from "@minimajs/server/mock";
import { bodyParser } from "@minimajs/server/plugins";
test("full app with plugins", async () => {
const app = createApp();
app.register(bodyParser());
app.post("/data", () => {
const data = body();
return { received: data };
});
const response = await app.handle(
createRequest("/data", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ value: 42 }),
})
);
const result = await response.json();
expect(result.received).toEqual({ value: 42 });
});Testing Module Encapsulation
ts
import { createRequest } from "@minimajs/server/mock";
test("module routes are isolated", async () => {
const app = createApp();
const adminModule = app.module("/admin");
adminModule.get("/users", () => ({ users: [] }));
const apiModule = app.module("/api");
apiModule.get("/users", () => ({ data: [] }));
// Test admin module
const res1 = await app.handle(createRequest("/admin/users"));
const data1 = await res1.json();
expect(data1).toHaveProperty("users");
// Test api module
const res2 = await app.handle(createRequest("/api/users"));
const data2 = await res2.json();
expect(data2).toHaveProperty("data");
});Testing Hooks
Testing Request Hooks
ts
import { createRequest } from "@minimajs/server/mock";
test("request hook modifies context", async () => {
const app = createApp();
app.register(
hook("request", () => {
context.set("requestId", "test-123");
})
);
app.get("/", () => {
const requestId = context.get("requestId");
return { requestId };
});
const response = await app.handle(createRequest("/"));
const data = await response.json();
expect(data.requestId).toBe("test-123");
});Testing Transform Hooks
ts
import { createRequest } from "@minimajs/server/mock";
test("transform hook wraps response", async () => {
const app = createApp();
app.register(
hook("transform", (data) => {
return { success: true, data };
})
);
app.get("/users", () => [{ id: 1 }]);
const response = await app.handle(createRequest("/users"));
const data = await response.json();
expect(data).toEqual({
success: true,
data: [{ id: 1 }],
});
});Testing Send Hooks
ts
import { createRequest } from "@minimajs/server/mock";
test("send hook adds custom header", async () => {
const app = createApp();
app.register(
hook("send", (ctx) => {
return response("hello world", {
headers: { "X-Custom": "value" },
});
})
);
app.get("/", () => "ok");
const response = await app.handle(createRequest("/"));
expect(response.headers.get("X-Custom")).toBe("value");
});Testing Plugins
Testing Custom Plugins
ts
import { createRequest } from "@minimajs/server/mock";
import { plugin } from "@minimajs/server";
const myPlugin = (options) => {
return app.register(
hook("request", () => {
context.set("pluginData", options.value);
})
);
};
test("custom plugin sets context", async () => {
const app = createApp();
app.register(myPlugin({ value: "test" }));
app.get("/", () => {
return { data: context.get("pluginData") };
});
const response = await app.handle(createRequest("/"));
const data = await response.json();
expect(data.data).toBe("test");
});Mocking
Mocking External Services
ts
import { createRequest } from "@minimajs/server/mock";
import { mock } from "bun:test";
test("mocked database query", async () => {
const mockDb = {
query: mock(() => Promise.resolve([{ id: 1, name: "Alice" }])),
};
const app = createApp();
app.get("/users", async () => {
const users = await mockDb.query();
return users;
});
const response = await app.handle(createRequest("/users"));
const data = await response.json();
expect(mockDb.query).toHaveBeenCalled();
expect(data).toHaveLength(1);
});Mocking Context Values
ts
import { createRequest } from "@minimajs/server/mock";
import { createContext } from "@minimajs/server";
test("mock context for testing", async () => {
const app = createApp();
const [getUserId, setUserId] = createContext();
// Set up context in request hook
app.register(
hook("request", () => {
setUserId("mock-user-123");
})
);
app.get("/profile", () => {
const userId = setUserId();
return { userId };
});
const response = await app.handle(createRequest("/profile"));
const data = await response.json();
expect(data.userId).toBe("mock-user-123");
});Best Practices
1. Test in Isolation
Create a fresh app instance for each test:
ts
import { beforeEach } from "bun:test";
describe("User API", () => {
let app;
beforeEach(() => {
app = createApp();
// Setup routes
app.get("/users", () => []);
});
test("test 1", async () => {
// app is fresh
});
test("test 2", async () => {
// app is fresh again
});
});2. Use Helper Functions
ts
// test-helpers.ts
import { createRequest } from "@minimajs/server/mock";
export async function makeRequest(app: App, path: string, options?: RequestInit) {
const response = await app.handle(createRequest(path, options));
const data = await response.json();
return { response, data };
}
// In tests
test("using helper", async () => {
const app = createApp();
app.get("/", () => ({ ok: true }));
const { response, data } = await makeRequest(app, "/");
expect(data.ok).toBe(true);
});3. Test Error Cases
ts
import { createRequest } from "@minimajs/server/mock";
test("handles errors gracefully", async () => {
const app = createApp();
app.get("/error", () => {
throw new Error("Something went wrong");
});
const response = await app.handle(createRequest("/error"));
expect(response.status).toBe(500);
});4. Test Edge Cases
ts
import { createRequest } from "@minimajs/server/mock";
describe("Edge cases", () => {
test("handles missing parameters", async () => {
const app = createApp();
app.get("/users/:id", () => {
const id = params.get("id");
if (!id) {
response.status(400);
return { error: "ID required" };
}
return { id };
});
const response = await app.handle(createRequest("/users/"));
expect(response.status).toBe(404); // Route doesn't match
});
test("handles malformed JSON", async () => {
const app = createApp();
app.register(bodyParser());
app.post("/data", () => {
try {
const data = body();
return data;
} catch (error) {
response.status(400);
return { error: "Invalid JSON" };
}
});
const response = await app.handle(
createRequest("/data", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: "{ invalid json }",
})
);
expect(response.status).toBe(400);
});
});5. Coverage Goals
Aim for good coverage of:
- ✅ Happy paths (expected inputs)
- ✅ Error paths (invalid inputs, exceptions)
- ✅ Edge cases (boundaries, empty values)
- ✅ Integration points (plugins, hooks)
Related Guides
- Error Handling - Testing error scenarios
- Hooks - Understanding lifecycle hooks
- Plugins - Creating testable plugins