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

MethodDescription
.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.