-
Notifications
You must be signed in to change notification settings - Fork 0
Factory Guide
ZSpec Bot edited this page Nov 26, 2025
·
1 revision
ZSpec includes a powerful Factory module inspired by Ruby's FactoryBot for generating test data.
const Factory = zspec.Factory;
// Define a factory
const UserFactory = Factory.define(User, .{
.id = Factory.sequence(u32),
.email = Factory.sequenceFmt("user{d}@example.com"),
.name = "John Doe",
.age = 25,
});
// Create instances
const user1 = UserFactory.build(.{});
const user2 = UserFactory.build(.{ .name = "Jane" });
// Create variants with traits
const AdminFactory = UserFactory.trait(.{ .role = "admin" });Define a factory with default values:
const User = struct {
id: u32,
name: []const u8,
email: []const u8,
active: bool,
};
const UserFactory = Factory.define(User, .{
.id = 1,
.name = "Test User",
.email = "test@example.com",
.active = true,
});Auto-incrementing numeric values:
const UserFactory = Factory.define(User, .{
.id = Factory.sequence(u32), // 1, 2, 3, ...
.name = "User",
});
const user1 = UserFactory.build(.{}); // id: 1
const user2 = UserFactory.build(.{}); // id: 2
const user3 = UserFactory.build(.{}); // id: 3Formatted sequence strings:
const UserFactory = Factory.define(User, .{
.id = Factory.sequence(u32),
.email = Factory.sequenceFmt("user{d}@example.com"),
});
const user1 = UserFactory.build(.{}); // email: "user1@example.com"
const user2 = UserFactory.build(.{}); // email: "user2@example.com"Important: Use arena allocator to avoid memory leaks:
test "with arena allocator" {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const alloc = arena.allocator();
const user = UserFactory.buildWith(alloc, .{});
}Create factory variants:
const UserFactory = Factory.define(User, .{
.name = "John",
.role = "user",
.active = true,
});
const AdminFactory = UserFactory.trait(.{ .role = "admin" });
const InactiveFactory = UserFactory.trait(.{ .active = false });
const admin = AdminFactory.build(.{}); // role: "admin"
const inactive = InactiveFactory.build(.{}); // active: false// Build value
const user = UserFactory.build(.{});
// Build pointer (heap-allocated)
const user_ptr = UserFactory.buildPtr(.{});
defer std.testing.allocator.destroy(user_ptr);
// With custom allocator
const user = UserFactory.buildWith(my_allocator, .{});
const user_ptr = UserFactory.buildPtrWith(my_allocator, .{});Reset all sequence counters:
test "tests:beforeAll" {
Factory.resetSequences();
}Compute values at build time:
const ItemFactory = Factory.define(Item, .{
.value = Factory.lazy(getValue),
});
fn getValue() u32 {
return 42;
}const ItemFactory = Factory.define(Item, .{
.data = Factory.lazyAlloc(getData),
});
fn getData(alloc: std.mem.Allocator) []const u8 {
return alloc.dupe(u8, "computed") catch unreachable;
}Create nested factories:
const AddressFactory = Factory.define(Address, .{
.street = "123 Main St",
.city = "Springfield",
});
const CompanyFactory = Factory.define(Company, .{
.name = "Acme Inc",
.address = Factory.assoc(AddressFactory),
});
const company = CompanyFactory.build(.{});
// company.address is automatically createdpub const MyTests = struct {
test "tests:beforeAll" {
Factory.resetSequences();
}
test "test 1" {
const user = UserFactory.build(.{});
// user.id == 1
}
test "test 2" {
const user = UserFactory.build(.{});
// user.id == 2 (sequence continues)
}
};pub const MyTests = struct {
test "tests:before" {
Factory.resetSequences();
}
test "test 1" {
const user = UserFactory.build(.{});
// user.id == 1
}
test "test 2" {
const user = UserFactory.build(.{});
// user.id == 1 (reset each test)
}
};// Base factory
const UserFactory = Factory.define(User, .{
.id = Factory.sequence(u32),
.name = "User",
.role = "user",
});
// Specialized variants
const AdminFactory = UserFactory.trait(.{ .role = "admin" });
const ModeratorFactory = UserFactory.trait(.{ .role = "moderator" });
const GuestFactory = UserFactory.trait(.{ .role = "guest" });
// Chain traits
const InactiveAdminFactory = AdminFactory.trait(.{ .active = false });Recommended for tests using sequenceFmt:
pub const MyTests = struct {
var arena: std.heap.ArenaAllocator = undefined;
var test_alloc: std.mem.Allocator = undefined;
test "tests:before" {
arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
test_alloc = arena.allocator();
}
test "tests:after" {
arena.deinit();
}
test "my test" {
const user = UserFactory.buildWith(test_alloc, .{});
// No manual cleanup needed
}
};- Always reset sequences at appropriate scope (beforeAll or before)
- Use traits for variants instead of duplicating factory definitions
- Use arena allocators with sequenceFmt to avoid leak reports
- Keep factories close to types - define factories near struct definitions
-
Name factories consistently -
[Type]Factorypattern - Leverage sequences for unique identifiers
See examples/factory_test.zig for comprehensive examples.
- ECS Integration - Using factories with zig-ecs