Factories
Factories are a vital testing and seeding utility designed to generate large amounts of realistic mock data for your database models. Harpia comes with a powerful, built-in Factory engine that provides a fluent API for generating stubs, persisting records, and picking specific attributes.
Generating a Factory
Harpia provides a CLI generator to swiftly create factory templates for your models.
bun harpia generate factory user
Alternatively, you can use the shorthand:
bun g factory user
This command generates a new factory file in the app/database/factories/ directory.
Defining a Factory
Factories leverage the @harpiats/common package and the @faker-js/faker library to generate realistic random data (names, emails, addresses, etc.).
A typical factory definition looks like this:
// app/database/factories/user.factory.ts
import { Factory } from "@harpiats/common";
import { User } from "app/database";
const UserFactory = new Factory().define(User, (faker) => {
return {
name: faker.person.fullName(),
email: faker.internet.email(),
password: faker.internet.password(),
};
});
export { UserFactory };
In this definition, new Factory().define() binds the Prisma Client model (User) to the object blueprint defined inside the callback function.
The Factory API
Harpia’s native Factory class provides a fluent and highly flexible API out of the box.
makeStubbed()
If you only need the generated payload without actually persisting it to the database, use makeStubbed().
// Generates a mock object
const fakePayload = await UserFactory.makeStubbed();
// Generates an array of mock objects
const multiplePayloads = await UserFactory.makeStubbedMany(5);
create()
To generate the data and immediately persist it to your database, use the create() method.
// Creates and persists a single user
const user = await UserFactory.create();
// Creates and persists 5 users
const users = await UserFactory.createMany(5);
merge()
You can easily override any automatically generated fields by using the merge() method before creating or stubbing the data.
// Overrides the "role" field for this specific creation
const adminUser = await UserFactory.merge({ role: "ADMIN" }).create();
// You can chain it with stubs as well
const adminPayload = await UserFactory.merge({ role: "ADMIN" }).makeStubbed();
Pickable Promises
One of the most powerful features of the Harpia Factory engine is Pickable Promises. When you call create() or createMany(), the returned Promise includes utility methods (.pick() and .get()) that allow you to extract exactly what you need without declaring intermediary variables.
.get(field)
Extracts a single field’s value directly. This is incredibly useful for extracting IDs immediately after creation.
// Returns the string ID of the created user
const userId = await UserFactory.create().get("id");
// When used with createMany, it returns an array of the extracted field
// string[]
const userIds = await UserFactory.createMany(3).get("id");
.pick(fields)
Returns an object containing only the specified fields.
// Returns: { id: "...", email: "..." }
const userData = await UserFactory.create().pick(["id", "email"]);
Using Factories in Tests
When writing End-to-End (E2E) or integration tests, Factories allow you to rapidly setup your database state before executing requests via the TestClient.
// modules/user/tests/show.spec.ts
import { describe, expect, it } from "bun:test";
import { TestClient } from "@harpiats/core";
import { app } from "start/server";
import { UserFactory } from "app/database/factories/user.factory";
const client = new TestClient(app);
describe("GET /users/:id", () => {
it("should return a user profile", async () => {
// 1. Arrange: Create a mock user, overriding the name, and grabbing the ID directly
const userId = await UserFactory.merge({ name: "Jane Doe" }).create().get("id");
// 2. Act: Fetch the user via the API using the extracted ID
const response = await client.get(`/users/${userId}`).execute();
const body = await response.json();
// 3. Assert: Verify the response
expect(response.status).toBe(200);
expect(body.data.name).toBe("Jane Doe");
});
});
Using factories ensures that each test is deterministic and independent, as you explicitly define the exact state required for the assertion to pass.