Facto is a fixtures library with a definition syntax. It allows Go developers to define fixtures in a declarative way using Go code instead of TOML/JSON/YAML. Inspired by factory_bot.
go get github.com/wawandco/factoFacto exposes a small API to define and use factories. Here's how to create a User factory:
// factories/user.go
package factories
import (
"github.com/wawandco/facto"
)
func UserFactory(h facto.Helper) facto.Product {
user := User{
Name: h.Faker.Name(),
}
return facto.Product(user)
}Once defined, use factories in your tests:
// users/user_test.go
package user_test
import (
"your/package/factories"
)
func TestUser(t *testing.T) {
// Build creates an object without persisting it
user := facto.Build(factories.UserFactory).(models.User)
err := db.Create(&user)
// use user for test purposes ...
// Create builds and persists the object to the database
p, err := facto.Create(factories.UserFactory)
user := p.(models.User)
// user is already stored in the database
}When you need multiple instances, use BuildN or CreateN:
// test/user_test.go
package user_test
func TestMultipleUsers(t *testing.T) {
// Build 10 users without persisting
users := facto.BuildN(factories.UserFactory, 10).([]models.User)
// Create 10 users and persist them
p, err := facto.CreateN(factories.UserFactory, 10)
users = p.([]models.User)
}When running BuildN, use h.Index to differentiate objects:
// factories/user.go
package factories
import (
"fmt"
"github.com/wawandco/facto"
)
func UserFactory(h facto.Helper) facto.Product {
user := User{
Name: fmt.Sprintf("User %d", h.Index),
}
return facto.Product(user)
}h.Index starts at 0 when a factory is called individually.
Build objects that depend on other objects:
// factories/event.go
package factories
import (
"github.com/wawandco/facto"
)
func UserFactory(h facto.Helper) facto.Product {
user := User{
Name: h.Faker.FirstName(),
}
return facto.Product(user)
}
func EventFactory(h facto.Helper) facto.Product {
event := Event{
// Build a User within the Event factory
User: facto.Build(UserFactory).(User),
Type: "Something",
}
return facto.Product(event)
}Reference the same ID across multiple factories using NamedUUID:
// factories/user.go
package factories
import (
"github.com/wawandco/facto"
)
func UserFactory(h facto.Helper) facto.Product {
user := User{
// Creates or retrieves a UUID named "owner_id"
ID: h.NamedUUID("owner_id"),
Name: h.Faker.FirstName(),
}
return facto.Product(user)
}
// factories/event.go
package factories
import (
"github.com/wawandco/facto"
)
func EventFactory(h facto.Helper) facto.Product {
event := Event{
// Retrieves the same "owner_id" UUID if UserFactory was called,
// otherwise generates a new one
UserID: h.NamedUUID("owner_id"),
Type: "Something",
}
return facto.Product(event)
}By default, NamedUUID returns a 16-byte slice. To use a different UUID library, set h.UUIDGenerator:
// Using google/uuid
import "github.com/google/uuid"
func UserFactory(h facto.Helper) facto.Product {
h.UUIDGenerator = func() []byte {
return uuid.Must(uuid.NewRandom())[:]
}
user := User{
ID: h.NamedUUID("user_id"),
}
return facto.Product(user)
}
// Or using gofrs/uuid
import "github.com/gofrs/uuid/v5"
func CompanyFactory(h facto.Helper) facto.Product {
h.UUIDGenerator = func() []byte {
u, _ := uuid.NewV4()
return u.Bytes()
}
company := Company{
ID: h.NamedUUID("company_id"),
}
return facto.Product(company)
}If UUIDGenerator is nil (the default), Facto uses a built-in crypto/rand-based generator.
Generate realistic-looking test data using h.Faker:
// factories/event.go
package factories
import (
"github.com/wawandco/facto"
)
func EventFactory(h facto.Helper) facto.Product {
event := Event{
Name: h.Faker.FirstName(),
Type: "Sports",
ContactEmail: h.Faker.Email(),
Company: h.Faker.Company(),
Address: h.Faker.Address().Address,
}
return facto.Product(event)
}Fake data is powered by Gofakeit. See the full list of generators.
Randomly select from a list of values:
// factories/event.go
package factories
import (
"github.com/wawandco/facto"
)
func EventFactory(h facto.Helper) facto.Product {
event := Event{
Name: h.Faker.FirstName(),
// Randomly picks one of the provided values
Type: h.OneOf(TypeSports, TypeMusic, TypeConcert).(EventType),
}
return facto.Product(event)
}Install the Facto CLI to generate factory files:
go install github.com/wawandco/facto/cmd/facto@latestGenerate a factory:
facto generate user
# Generates factories/user.goFacto provides a plugin for the Ox CLI:
// cmd/ox/main.go
package main
import (
// ...
fox "github.com/wawandco/facto/ox"
)
func main() {
cli.Use(fox.Plugin{}) // Add the facto plugin
err := cli.Run(context.Background(), os.Args)
if err != nil {
log.Fatal(err)
}
}Now factory appears as a generator:
$ ox generate
Available Generators:
Name Plugin
---- ------
factory facto # <-- this oneGenerate factories with Ox:
ox generate factory [name]