Test Client
TestClient is Harpia’s built-in HTTP client designed specifically for automated testing. It lets you make real requests against your application, including full middleware and route execution, without needing a live server.
Setup
Instantiate TestClient by passing your application instance. The client reads the running server’s hostname and port automatically.
import { describe, test, expect, beforeAll, afterAll } from "bun:test";
import harpia, { TestClient } from "@harpiats/core";
const app = harpia();
app.get("/hello", (req, res) => {
res.json({ message: "Hello, World!" });
});
app.listen({ port: 3000 });
const client = new TestClient(app);
Making Requests
TestClient follows a fluent, chainable API. Start with an HTTP method, optionally add headers or a body, and finish with execute() to dispatch the request.
GET Request
test("should return hello message", async () => {
const response = await client.get("/hello").execute();
const body = await response.json();
expect(response.status).toBe(200);
expect(body.message).toBe("Hello, World!");
});
POST with JSON Body
Use .json() to set a JSON payload. The Content-Type: application/json header is added automatically.
test("should create a user", async () => {
const response = await client
.post("/users")
.json({ name: "Alice", email: "alice@example.com" })
.execute();
expect(response.status).toBe(201);
});
Setting Custom Headers
Use .set() to add any header to the request. This is useful for passing authentication tokens.
test("should access a protected route", async () => {
const response = await client
.get("/dashboard")
.set("Authorization", "Bearer my-token")
.execute();
expect(response.status).toBe(200);
});
Query Parameters
Use .query() to append query string parameters to the URL.
test("should search for users", async () => {
const response = await client
.get("/users")
.query("name", "Alice")
.query("page", "1")
.execute();
// Dispatches to: GET /users?name=Alice&page=1
expect(response.status).toBe(200);
});
Form Data
Use .formData() to send multipart/form-data payloads. You can chain it with .file() or .files() to mix text fields and file uploads in the same form.
test("should update profile with avatar", async () => {
const avatarBlob = new Blob(["...image bytes..."], { type: "image/png" });
const response = await client
.post("/profile")
.formData({ username: "alice" })
.file({ avatar: avatarBlob })
.execute();
expect(response.status).toBe(200);
});
File Uploads
Use .file() for a single file and .files() for multiple files under the same field name. You can pass either a file path (string) or a Blob object.
test("should upload a document from disk", async () => {
const response = await client
.post("/documents")
.file({ document: "/absolute/path/to/report.pdf" })
.execute();
expect(response.status).toBe(200);
});
test("should upload multiple photos", async () => {
const photo = new Blob(["..."], { type: "image/jpeg" });
const response = await client
.post("/gallery")
.files({ photos: [photo, photo] })
.execute();
expect(response.status).toBe(200);
});
Raw Body
Use .send() to pass a raw string as the request body. The Content-Length header is set automatically.
test("should send raw XML body", async () => {
const response = await client
.post("/webhook")
.set("Content-Type", "application/xml")
.send("<event><type>purchase</type></event>")
.execute();
expect(response.status).toBe(200);
});
API Reference
| Method | Description |
|---|---|
.get(url) | Sets the request method to GET |
.post(url) | Sets the request method to POST |
.put(url) | Sets the request method to PUT |
.patch(url) | Sets the request method to PATCH |
.delete(url) | Sets the request method to DELETE |
.options(url) | Sets the request method to OPTIONS |
.head(url) | Sets the request method to HEAD |
.set(name, value) | Adds a request header |
.query(name, value) | Appends a query parameter to the URL |
.json(data) | Sets a JSON body (adds Content-Type: application/json) |
.formData(data) | Sets a multipart form body |
.file(files) | Attaches a single file per field to the form body |
.files(files) | Attaches multiple files per field to the form body |
.send(data) | Sets a raw string body (adds Content-Length) |
.execute() | Dispatches the request and returns the response |
Body Type Conflicts
You must choose between sending a JSON payload .json() or a form-based payload .formData(), .file(). Combining these in a single request will trigger an error.