{"id":42585284,"url":"https://github.com/b3ndoi/factory-go","last_synced_at":"2026-01-28T22:40:29.782Z","repository":{"id":320351459,"uuid":"1081104163","full_name":"b3ndoi/factory-go","owner":"b3ndoi","description":"Laravel-like test factories for Go with generics. ","archived":false,"fork":false,"pushed_at":"2025-10-23T09:50:44.000Z","size":47,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-10-23T10:31:14.634Z","etag":null,"topics":["factory-pattern","golang","golang-testing","laravel-inspired"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/b3ndoi.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-10-22T10:12:22.000Z","updated_at":"2025-10-23T09:55:26.000Z","dependencies_parsed_at":"2025-10-23T10:31:21.029Z","dependency_job_id":null,"html_url":"https://github.com/b3ndoi/factory-go","commit_stats":null,"previous_names":["b3ndoi/factory-go"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/b3ndoi/factory-go","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/b3ndoi%2Ffactory-go","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/b3ndoi%2Ffactory-go/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/b3ndoi%2Ffactory-go/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/b3ndoi%2Ffactory-go/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/b3ndoi","download_url":"https://codeload.github.com/b3ndoi/factory-go/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/b3ndoi%2Ffactory-go/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28853774,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-28T15:15:36.453Z","status":"ssl_error","status_checked_at":"2026-01-28T15:15:13.020Z","response_time":57,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["factory-pattern","golang","golang-testing","laravel-inspired"],"created_at":"2026-01-28T22:40:29.718Z","updated_at":"2026-01-28T22:40:29.776Z","avatar_url":"https://github.com/b3ndoi.png","language":"Go","readme":"# Factory-Go\n\n[![CI](https://github.com/b3ndoi/factory-go/actions/workflows/ci.yml/badge.svg)](https://github.com/b3ndoi/factory-go/actions/workflows/ci.yml)\n[![CodeQL](https://github.com/b3ndoi/factory-go/actions/workflows/codeql.yml/badge.svg)](https://github.com/b3ndoi/factory-go/actions/workflows/codeql.yml)\n[![Go Reference](https://pkg.go.dev/badge/github.com/b3ndoi/factory-go.svg)](https://pkg.go.dev/github.com/b3ndoi/factory-go)\n[![Go Report Card](https://goreportcard.com/badge/github.com/b3ndoi/factory-go)](https://goreportcard.com/report/github.com/b3ndoi/factory-go)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![Tests](https://img.shields.io/badge/tests-60%20passing-brightgreen)](https://github.com/b3ndoi/factory-go/actions/workflows/ci.yml)\n[![Coverage](https://img.shields.io/badge/coverage-89%25-brightgreen)](https://github.com/b3ndoi/factory-go/actions/workflows/ci.yml)\n\n**Type-safe factories for tests and seed data with Laravel-inspired ergonomics.**\n\n- **Type-safe with generics** - No `interface{}` or type assertions, full compile-time checking\n- **Built-in relationships** - For, Has, HasAttached, Recycle for all relationship patterns\n- **JSON/Raw for API tests** - Direct JSON output, separate API vs domain fields with WithRawDefaults\n\n**[Quick Start](#installation--quick-start) • [Concepts](#core-concepts) • [Performance](#performance) • [Sequences](#sequence---cycling-through-attributes) • [States](#named-states) • [Raw/JSON](#raw-attributes--json-api-testing) • [Relationships](#relationships) • [Hooks](#lifecycle-hooks) • [API Reference](#api-reference)**\n\n---\n\n## Why Generics Over Reflection?\n\nFactory-Go uses **Go generics** (Go 1.21+) instead of reflection like older libraries:\n\n| Advantage | Generics | Reflection (old libs) |\n|-----------|----------|----------------------|\n| Type safety | ✅ Compile-time | ❌ Runtime only |\n| Return types | `User` directly | `interface{}` + cast |\n| Performance | ✅ No overhead | ❌ Reflection penalty |\n| IDE support | ✅ Full autocomplete | ❌ Limited |\n| Error detection | ✅ At compile time | ❌ At runtime |\n\n**Example comparison:**\n```go\n// Old libraries (bluele/factory)\nuser := factory.Create(\"User\").(*User)  // Type assertion required!\n\n// Factory-Go\nuser := userFactory.Make()  // Type-safe, no assertions\n```\n\n---\n\n## Features\n\n- 🎯 **Type-safe** - Uses Go generics for full type safety\n- 🔧 **Flexible** - Support for defaults, traits, and custom persistence\n- 🚀 **Laravel-inspired** - Familiar API if you've used Laravel factories\n- 🧪 **Test-friendly** - Perfect for seeding test databases or creating in-memory fixtures\n- 🔄 **Faker integration** - Easy integration with faker libraries for realistic data\n- 🐛 **Debugging tools** - Tap() for inspecting items during creation\n- 🌍 **Environment-aware** - When()/Unless() for conditional behavior\n- 🔁 **Factory variations** - Clone() for creating factory variations\n- ⚡ **Must* variants** - Panic on error for cleaner test code\n- 📦 **JSON support** - Direct JSON output for API testing\n- 🔗 **Relationships** - Built-in support for model relationships\n\n## Installation \u0026 Quick Start\n\n**Requirements:** Go 1.21+ (uses generics)\n\n```bash\n# Install latest version\ngo get github.com/b3ndoi/factory-go@latest\n\n# Or specific version\ngo get github.com/b3ndoi/factory-go@v1.0.0\n```\n\n**Versioning:** Factory-Go follows [Semantic Versioning](https://semver.org/). Breaking changes only in major versions (v2.x.x). See [CHANGELOG.md](CHANGELOG.md) for version history.\n\n```go\n// Import\nimport \"github.com/b3ndoi/factory-go/factory\"\n\n// Define your model\ntype User struct {\n    ID    string\n    Name  string\n    Email string\n}\n\n// Create a factory\nuserFactory := factory.New(func(seq int64) User {\n    return User{\n        Name:  fmt.Sprintf(\"User %d\", seq),\n        Email: fmt.Sprintf(\"user%d@example.com\", seq),\n    }\n})\n\n// Use it\nuser := userFactory.Make()                    // Single in-memory\nusers := userFactory.Count(10).Make()         // Multiple items\n```\n\n## 📖 Examples\n\nCheck out the `/examples` directory for comprehensive examples:\n\n- **[basic/](examples/basic)** - Getting started with Factory-Go ⭐\n- **[api_testing/](examples/api_testing)** - HTTP API testing with RawJSON\n- **[database_seeding/](examples/database_seeding)** - Seeding DBs with relationships\n- **[complete_app/](examples/complete_app)** - Full-featured blog application\n- **[faker_integration/](examples/faker_integration)** - Realistic data with faker\n\nEach example is runnable: `cd examples/basic \u0026\u0026 go run main.go`\n\n## Imports Used in Snippets\n\n```go\nimport (\n    \"bytes\"\n    \"context\"\n    \"fmt\"\n    \"log\"\n    \"net/http\"\n    \"strings\"\n    \"time\"\n    \n    \"github.com/b3ndoi/factory-go/factory\"\n    \"github.com/brianvoe/gofakeit/v6\"  // Optional: for realistic fake data\n)\n```\n\n## Core Concepts\n\n- **Defaults** - Base values for all items (`WithDefaults` for domain, `WithRawDefaults` for API-only fields)\n- **Traits** - Modifications applied globally (`WithTraits`) or per-call (`Make(trait)`)\n- **States** - Named, reusable configurations (`DefineState(\"admin\", trait)`, then `.State(\"admin\")`)\n- **Sequences** - Cycle through values for variety (`Sequence(trait1, trait2)` alternates per item)\n- **Raw/RawJSON** - Build without persistence, with JSON output for API testing\n- **Create/Persist** - Save to database with `WithPersist`, hooks with `BeforeCreate/AfterCreate`\n- **Relationships** - `For` (each child gets own parent), `Recycle` (shared parent), `Has` (parent with children), `HasAttached` (many-to-many with pivot)\n- **Clone/Reset** - `Clone()` deep-copies factory, `ResetSequence()` resets counter for test isolation\n\n### Thread Safety\n\n**All configuration methods return a new factory instance; existing instances are immutable and safe for concurrent use.**\n\n- ✅ **Immutable configuration** - `WithDefaults`, `State`, `Sequence`, etc. return new factories; originals unchanged\n- ✅ **Sequence counter** - Uses `sync/atomic` for thread-safe increments across goroutines\n- ✅ **Read-only after setup** - Once configured, factory internals are read-only\n- ⚠️ **Hooks caveat** - Your `BeforeCreate`/`AfterCreate` hooks must be thread-safe if accessing shared state\n- 💡 **Best practice** - For parallel tests, use `Clone()` per test or `ResetSequence()` in setup for predictable sequences\n\n**Note:** All configuration methods return a new factory; internal collections (slices, maps) are copied so previously created factories remain safe to use concurrently.\n\n## Performance\n\nFactory-Go is designed for test data generation and performs excellently:\n\n| Operation | Time/op | Notes |\n|-----------|---------|-------|\n| `Make()` | ~160ns | 6M+ items/sec |\n| `MakeMany(10)` | ~2.2μs | Linear scaling |\n| `MakeMany(100)` | ~18μs | Linear scaling |\n| `Create()` | ~237ns | With persistence |\n| `Clone()` | ~75ns | Very cheap |\n| `RawJSON()` | ~351ns | With marshaling |\n\n**Key characteristics:**\n- ✅ **Linear scaling** - MakeMany(100) = 10x MakeMany(10)\n- ✅ **Minimal overhead** - States, sequences add \u003c10ns\n- ✅ **Thread-safe** - Parallel execution scales well\n- ✅ **Efficient cloning** - Only 75ns for deep copy\n\n**vs Manual helpers:** Factory-Go is ~27% slower but provides 35+ features. The small overhead is well worth the type safety, reusability, and developer experience.\n\nSee [BENCHMARKS.md](BENCHMARKS.md) for detailed results and profiling guide.\n\n## Quick Reference\n\n```go\n// Setup\nfactory := factory.New(makeFn).\n    WithDefaults(trait).           // Default values\n    WithRawDefaults(trait).        // Only for Raw/JSON\n    DefineState(\"admin\", trait).   // Named states\n    Sequence(trait1, trait2).      // Cycle patterns\n    WithPersist(persistFn).        // DB persistence\n    BeforeCreate(hookFn).          // Before hooks\n    AfterCreate(hookFn).           // After hooks\n    Tap(inspectFn).                // Debug/log\n    When(condition, trait).        // Conditional\n    Unless(condition, trait)       // Inverse conditional\n\n// Create single\nuser := factory.Make()                    // In-memory\nuser := factory.Raw()                     // With rawDefaults\njson := factory.MustRawJSON()             // As JSON (panic on error)\nuser := factory.MustCreate(ctx)           // Persist (panic on error)\n\n// Create multiple  \nusers := factory.MakeMany(10)             // In-memory\nusers := factory.Count(10).Make()         // Fluent API\nusers := factory.Count(5).State(\"admin\").MustCreate(ctx)\n\n// Relationships\npost := factory.For(postFactory, userFactory, linkFn).Make()                     // Each child gets own parent\nposts := factory.Recycle(postFactory, user, linkFn).Count(5).Make()              // All share same parent\nuser, posts := factory.Has(userFactory, postFactory, 5, linkFn).MustCreate(ctx)  // Parent with children\nuser, roles, pivots := factory.HasAttached(userF, roleF, pivotF, 3, linkFn).MustCreate(ctx)  // Many-to-many\n\n// Utilities\nfactory.Clone()          // Deep copy with reset sequence\nfactory.ResetSequence()  // Reset sequence counter to 0 (next build uses seq=1)\n```\n\n## Quick Start\n\n```go\nimport \"github.com/b3ndoi/factory-go/factory\"\n\n// Define your model\ntype User struct {\n    ID    string\n    Name  string\n    Email string\n    Role  string\n}\n\n// Create a factory\nuserFactory := factory.New(func(seq int64) User {\n    return User{\n        Name:  fmt.Sprintf(\"User %d\", seq),\n        Email: fmt.Sprintf(\"user%d@example.com\", seq),\n        Role:  \"user\",\n    }\n})\n\n// Make an in-memory user (not persisted)\nuser := userFactory.Make()\n\n// Make 10 users at once\nusers := userFactory.MakeMany(10)\n// Or use the fluent Count() API\nusers = userFactory.Count(10).Make()\n\n// Make with custom traits\nadmin := userFactory.Make(func(u *User) {\n    u.Role = \"admin\"\n})\n```\n\n## Sequence - Cycling Through Attributes\n\nThe `Sequence` method allows you to cycle through different attribute values when creating multiple models, just like [Laravel's sequence()](https://laravel.com/docs/12.x/eloquent-factories#sequences):\n\n```go\n// Alternate between admin and user roles\nuserFactory := factory.New(func(seq int64) User {\n    return User{\n        Name:  fmt.Sprintf(\"User %d\", seq),\n        Email: fmt.Sprintf(\"user%d@example.com\", seq),\n    }\n}).Sequence(\n    func(u *User) { u.Role = \"admin\" },\n    func(u *User) { u.Role = \"user\" },\n)\n\n// Creates: admin, user, admin, user, admin\nusers := userFactory.MakeMany(5)\n```\n\n### Advanced Sequences\n\nSequences work with any number of states and automatically cycle:\n\n```go\n// Three-state sequence\nstatusFactory := factory.New(func(seq int64) Order {\n    return Order{Number: seq}\n}).Sequence(\n    func(o *Order) { o.Status = \"pending\" },\n    func(o *Order) { o.Status = \"processing\" },\n    func(o *Order) { o.Status = \"completed\" },\n)\n\n// Creates 10 orders cycling through: pending, processing, completed, pending...\norders := statusFactory.MakeMany(10)\n```\n\n### Sequence with Per-Call Overrides\n\nPer-call traits always override sequence values. **Note:** Sequences advance on every build, even when overridden.\n\n```go\nfactory := factory.New(makeFn).Sequence(\n    func(u *User) { u.Role = \"admin\" },\n    func(u *User) { u.Role = \"user\" },\n)\n\nu1 := factory.Make()                                      // Role: \"admin\" (sequence step 1)\nu2 := factory.Make(func(u *User) { u.Role = \"guest\" })   // Role: \"guest\" (override; sequence still advances)\nu3 := factory.Make()                                      // Role: \"admin\" (sequence step 3, cycles back to first item)\n```\n\n**With a 2-item sequence, the 3rd build cycles back to the 1st item** (3 % 2 = 1, which maps to index 0).\n\n## Named States\n\nNamed states let you define reusable state configurations, similar to [Laravel's state methods](https://laravel.com/docs/12.x/eloquent-factories#factory-states):\n\n```go\n// Define named states\nuserFactory := factory.New(func(seq int64) User {\n    return User{\n        Name:  fmt.Sprintf(\"User %d\", seq),\n        Email: fmt.Sprintf(\"user%d@example.com\", seq),\n        Role:  \"user\",\n    }\n}).DefineState(\"admin\", func(u *User) {\n    u.Role = \"admin\"\n    u.Permissions = []string{\"read\", \"write\", \"delete\"}\n}).DefineState(\"moderator\", func(u *User) {\n    u.Role = \"moderator\"\n    u.Permissions = []string{\"read\", \"write\"}\n}).DefineState(\"verified\", func(u *User) {\n    u.EmailVerifiedAt = time.Now()\n})\n\n// Use named states - much cleaner than inline functions!\nadmin := userFactory.State(\"admin\").Make()\nverifiedAdmin := userFactory.State(\"admin\").State(\"verified\").Make()\n\n// Works with all factory methods\nadmins, _ := userFactory.State(\"admin\").CreateMany(ctx, 5)\n```\n\n### Benefits of Named States\n\n1. **Reusable** - Define once, use everywhere\n2. **Readable** - `State(\"admin\")` is clearer than inline functions\n3. **Chainable** - Combine multiple states easily\n4. **Type-safe** - Panics if you reference an undefined state\n\n```go\n// Chain multiple states\nuser := factory.State(\"admin\").State(\"verified\").State(\"premium\").Make()\n\n// Override state with per-call traits\ncustomAdmin := factory.State(\"admin\").Make(func(u *User) {\n    u.Name = \"Custom Admin Name\"\n})\n```\n\n## Using WithDefaults (Faker Integration)\n\nThe `WithDefaults` method is perfect for integrating faker libraries or defining reusable default values:\n\n```go\nimport (\n    \"github.com/b3ndoi/factory-go/factory\"\n    \"github.com/brianvoe/gofakeit/v6\"\n)\n\n// Create factory with faker defaults\nuserFactory := factory.New(func(seq int64) User {\n    return User{} // Empty struct\n}).WithDefaults(func(u *User) {\n    // Use faker library for realistic data\n    u.Name = gofakeit.Name()\n    u.Email = gofakeit.Email()\n    u.Role = \"user\"\n})\n\n// Each call generates unique fake data\nuser1 := userFactory.Make() // John Doe, john@example.com\nuser2 := userFactory.Make() // Jane Smith, jane@example.com\n\n// Override specific fields\nadmin := userFactory.Make(func(u *User) {\n    u.Role = \"admin\" // Keeps fake name and email\n})\n```\n\n## Trait Application Order\n\nTraits are applied in a specific order, allowing for flexible overrides:\n\n| Priority | Make/Create | Raw/RawJSON | Source | Purpose |\n|----------|-------------|-------------|--------|---------|\n| 1 | ✅ | ✅ | `makeFn` | Base struct |\n| 2 | ✅ | ✅ | `WithDefaults` | Faker/default values |\n| 3 | ❌ | ✅ | `WithRawDefaults` | API-only fields (passwords, tokens) |\n| 4 | ✅ | ✅ | `WithTraits` | Global modifications |\n| 5 | ✅ | ✅ | `Sequence` | Cycle through patterns |\n| 6 | ✅ | ✅ | `State` | Apply named states |\n| 7 | ✅ | ✅ | Per-call traits | Specific customizations |\n| 8 | ✅ | ✅ | `Tap` | Inspect only (doesn't modify) |\n\n**Key insights:** \n- Later steps override earlier ones. Per-call traits always win.\n- Sequence advances on every build (including Make, Raw, Create), regardless of states or overrides.\n\n```go\nuserFactory := factory.New(func(seq int64) User {\n    return User{Role: \"guest\"} // 1. Base\n}).WithDefaults(func(u *User) {\n    u.Role = \"user\" // 2. Overrides base\n    u.Name = gofakeit.Name()\n}).WithTraits(func(u *User) {\n    u.Email = strings.ToLower(u.Email) // 3. Modifies defaults\n}).Sequence(\n    func(u *User) { u.Role = \"admin\" },  // 4a. First item\n    func(u *User) { u.Role = \"moderator\" }, // 4b. Second item (cycles)\n)\n\n// Per-call trait overrides everything\nsuperuser := userFactory.Make(func(u *User) {\n    u.Role = \"superuser\" // 5. Overrides all previous (including sequence)\n})\n```\n\n## Raw Attributes \u0026 JSON (API Testing)\n\nGet fully built objects without persisting - perfect for testing APIs:\n\n### Raw() with Separate Defaults\n\nUse `WithRawDefaults()` to add fields only for raw/JSON output (not persistence):\n\n```go\ntype User struct {\n    ID       string\n    Name     string\n    Email    string\n    Password string `json:\"password,omitempty\"` // Only for API, not DB\n}\n\nuserFactory := factory.New(func(seq int64) User {\n    return User{\n        Name:  fmt.Sprintf(\"User %d\", seq),\n        Email: fmt.Sprintf(\"user%d@example.com\", seq),\n    }\n}).WithRawDefaults(func(u *User) {\n    // This ONLY applies to Raw/RawJSON, not Make/Create\n    u.Password = \"test-password-123\"\n})\n\n// Make() does NOT include rawDefaults\nuser := userFactory.Make()  // Password: \"\"\n\n// Raw() DOES include rawDefaults\nrawUser := userFactory.Raw() // Password: \"test-password-123\"\n```\n\n### RawJSON() for API Testing\n\nGet JSON directly for HTTP tests:\n\n```go\nimport (\n    \"bytes\"\n    \"net/http\"\n)\n\n// Generate JSON payload\njsonData := userFactory.MustRawJSON()\n\n// Use in HTTP request\nreq, _ := http.NewRequest(http.MethodPost, \"/api/register\", bytes.NewReader(jsonData))\nreq.Header.Set(\"Content-Type\", \"application/json\")\nresp, _ := http.DefaultClient.Do(req)\n\n// Multiple objects as JSON array\njsonArray := userFactory.Count(10).MustRawJSON()\n\n// Works with states\nadminJSON := userFactory.State(\"admin\").MustRawJSON()\n```\n\n### Real-World Example\n\n```go\n// Testing user registration endpoint\nfunc TestUserRegistration(t *testing.T) {\n    userFactory := factory.New(func(seq int64) UserRequest {\n        return UserRequest{\n            Username: fmt.Sprintf(\"user%d\", seq),\n            Email:    fmt.Sprintf(\"user%d@test.com\", seq),\n        }\n    }).WithRawDefaults(func(r *UserRequest) {\n        r.Password = \"ValidPassword123!\"\n        r.PasswordConfirm = \"ValidPassword123!\"\n    })\n    \n    // Get JSON payload for API test\n    payload, _ := userFactory.RawJSON()\n    \n    // POST to registration endpoint\n    resp := testClient.POST(\"/api/register\", payload)\n    assert.Equal(t, 201, resp.StatusCode)\n}\n```\n\n## Persistence with Create\n\n```go\n// Set up persistence\nuserFactory := factory.New(func(seq int64) User {\n    return User{\n        Name:  fmt.Sprintf(\"User %d\", seq),\n        Email: fmt.Sprintf(\"user%d@example.com\", seq),\n    }\n}).WithPersist(func(ctx context.Context, u *User) (*User, error) {\n    // Your database logic\n    u.ID = uuid.New().String()\n    err := db.InsertUser(ctx, u)\n    return u, err\n})\n\n// Create and persist a single user\nuser, err := userFactory.Create(context.Background())\n\n// Create and persist multiple users\nusers, err := userFactory.CreateMany(context.Background(), 10)\n\n// Create with custom traits\nadmin, err := userFactory.Create(context.Background(), func(u *User) {\n    u.Role = \"admin\"\n})\n```\n\n## Reset Sequence (Test Isolation)\n\nReset the sequence counter to get predictable data in tests:\n\n```go\nfunc TestUserCreation(t *testing.T) {\n    // Reset before each test for predictable sequence numbers\n    userFactory.ResetSequence()\n    \n    user := userFactory.Make()\n    // Always \"User 1\" because sequence was reset\n    assert.Equal(t, \"User 1\", user.Name)\n}\n\n// Chainable\nusers := userFactory.ResetSequence().MakeMany(5)\n// Creates: User 1, User 2, User 3, User 4, User 5\n```\n\n## Count() - Fluent API\n\nUse the fluent `Count()` API for a more Laravel-like syntax:\n\n```go\n// Instead of MakeMany(10)\nusers := userFactory.Count(10).Make()\n\n// Works with Create too\nusers, err := userFactory.Count(5).Create(ctx)\n\n// Fully chainable with states\nadmins := userFactory.Count(3).State(\"admin\").Make()\nverifiedUsers, _ := userFactory.Count(10).State(\"verified\").Create(ctx)\n\n// With per-call traits\ncustomUsers := userFactory.Count(5).Make(func(u *User) {\n    u.Active = true\n})\n\n// Raw() also works\nrawData := userFactory.Count(20).Raw()\n\n// Times() is an alias for Count()\nusers = userFactory.Times(3).Make()\n```\n\nThe `CountedFactory` returned by `Count()` has these methods:\n- `Make(...traits) []T` - Build count items in-memory\n- `Create(ctx, ...traits) ([]*T, error)` - Build and persist count items\n- `Raw(...traits) []T` - Build count items **with `rawDefaults` applied**\n- `RawJSON(...traits) ([]byte, error)` - Build count items and return JSON array\n- `State(name) *CountedFactory[T]` - Apply a named state (chainable)\n\n## Relationships\n\nFactory-Go provides powerful relationship helpers for all common database relationship patterns.\n\n### Quick Decision Guide\n\n| Pattern | Function | When to Use | Example |\n|---------|----------|-------------|---------|\n| Each child needs different parent | `For()` | Posts by different authors | 10 posts → 10 users |\n| Children share same parent | `Recycle()` / `ForModel()` | Posts by one author | 10 posts → 1 user |\n| Create parent with children | `Has()` | User with posts | 1 user → 5 posts |\n| Many-to-many with pivot | `HasAttached()` | User with roles | 1 user → 3 roles + 3 pivots |\n\n### For() - Belongs To (Each Item Gets Its Own Parent)\n\nThe `For()` function creates a new related model for each item:\n\n```go\ntype Post struct {\n    ID       string\n    Title    string\n    AuthorID string\n}\n\n// Each post gets its own newly created user\npost := factory.For(postFactory, userFactory, func(p *Post, u *User) {\n    p.AuthorID = u.ID\n}).Make()\n\n// Create multiple posts, each with their own user\nposts := factory.For(postFactory, userFactory, func(p *Post, u *User) {\n    p.AuthorID = u.ID\n}).MakeMany(3)\n// Creates 3 posts with 3 different users\n```\n\n### ForModel() / Recycle() - Belongs To (Shared Parent)\n\nUse an existing model instance across multiple children:\n\n```go\n// Create/get an existing user\nuser, _ := userFactory.Create(ctx)\n\n// All posts will belong to the same user\nposts := factory.ForModel(postFactory, user, func(p *Post, u *User) {\n    p.AuthorID = u.ID\n}).MakeMany(10)\n\n// Recycle() is an alias - more semantic name\nposts := factory.Recycle(postFactory, user, func(p *Post, u *User) {\n    p.AuthorID = u.ID\n}).Count(10).MustCreate(ctx)\n```\n\n### Has() - One-to-Many (Parent with Children)\n\nCreate a parent with multiple children (inverse of `For`):\n\n```go\n// Create 1 user with 5 posts\nuser, posts := factory.Has(userFactory, postFactory, 5, func(u *User, p *Post) {\n    p.AuthorID = u.ID\n}).Make()\n// Returns: user + []posts\n\n// With persistence\nuser, posts, err := factory.Has(userFactory, postFactory, 3, func(u *User, p *Post) {\n    p.AuthorID = u.ID\n}).Create(ctx)\n// Creates and saves 1 user + 3 posts\n\n// MustCreate variant\nuser, posts := factory.Has(userFactory, postFactory, 10, linkFn).MustCreate(ctx)\n```\n\n### HasAttached() - Many-to-Many with Pivot\n\nCreate parent with many-to-many relationships through a pivot table:\n\n```go\ntype UserRole struct {\n    UserID string\n    RoleID string\n    Active bool // Pivot field\n}\n\n// Create user with 3 roles and pivot records\nuser, roles, err := factory.HasAttached(\n    userFactory,\n    roleFactory,\n    userRoleFactory, // Pivot factory\n    3,\n    func(pivot *UserRole, user *User, role *Role) {\n        pivot.UserID = user.ID\n        pivot.RoleID = role.ID\n        pivot.Active = true\n    },\n).Create(ctx)\n// Creates: 1 user + 3 roles + 3 pivot records\n```\n\n### Relationship Pattern Summary\n\n| Pattern | Function | Use Case | Example |\n|---------|----------|----------|---------|\n| Belongs To (unique) | `For()` | Each child has different parent | Post → User (each post by different user) |\n| Belongs To (shared) | `ForModel()` / `Recycle()` | All children share same parent | Posts → User (all by same user) |\n| Has Many | `Has()` | Parent with multiple children | User → Posts (user with multiple posts) |\n| Many-to-Many | `HasAttached()` | Parent with many children + pivot | User → Roles (with pivot attributes) |\n\n## Must* Variants (Clean Test Code)\n\nPanic on error instead of returning - perfect for tests where you want to fail fast!\n\n```go\nfunc TestUserCreation(t *testing.T) {\n    // No error handling needed - panics on failure\n    user := userFactory.MustCreate(ctx)\n    assert.Equal(t, \"user@example.com\", user.Email)\n    \n    // Works with Count() too\n    users := userFactory.Count(10).MustCreate(ctx)\n    assert.Len(t, users, 10)\n    \n    // JSON variants\n    jsonData := userFactory.MustRawJSON()\n    jsonArray := userFactory.Count(5).MustRawJSON()\n}\n```\n\n**Available Must* methods:**\n- `MustCreate(ctx, ...traits)` - Create and panic on error\n- `MustCreateMany(ctx, count, ...traits)` - Create many and panic on error\n- `MustRawJSON(...traits)` - Get JSON and panic on marshal error\n- `MustRawManyJSON(count, ...traits)` - Get JSON array and panic on marshal error\n- `Count(n).MustCreate(ctx)` - Fluent API with Must\n\n## Tap() - Debugging \u0026 Inspection\n\nInspect or log items during creation without modifying them:\n\n```go\n// Debug what's being created\nuserFactory := factory.New(makeFn).Tap(func(u User) {\n    fmt.Printf(\"Creating: %+v\\n\", u)\n})\n\n// Count items\ncount := 0\nfactory.Tap(func(u User) { count++ }).MakeMany(10)\nfmt.Printf(\"Created %d users\\n\", count)\n\n// Log to file\nfactory.Tap(func(u User) {\n    log.Printf(\"User created: %s (%s)\", u.Name, u.Email)\n}).CreateMany(ctx, 5)\n\n// Validate during creation\nfactory.Tap(func(u User) {\n    if u.Email == \"\" {\n        panic(\"Email is required!\")\n    }\n}).Make()\n```\n\n**Key points:**\n- Non-intrusive - doesn't modify the item\n- Called for every Make(), Raw(), Create() operation\n- Useful for debugging, logging, validation, counting\n\n## When() / Unless() - Conditional Logic\n\nApply traits based on runtime conditions:\n\n```go\nisProd := os.Getenv(\"ENV\") == \"production\"\nisTest := !isProd\n\nuserFactory := factory.New(makeFn).\n    When(isProd, func(u *User) {\n        u.Email = faker.Email() // Real emails in production\n    }).\n    Unless(isProd, func(u *User) {\n        u.Email = \"test@example.com\" // Fixed email in test\n    }).\n    When(isTest, func(u *User) {\n        u.Active = false // Inactive users in tests\n    })\n\nuser := userFactory.Make() // Traits applied based on environment\n```\n\n### Real-World Examples\n\n```go\n// Database-specific behavior\nusePostgres := config.DB == \"postgres\"\n\nfactory.\n    When(usePostgres, func(u *User) {\n        u.CreatedAt = time.Now()\n    }).\n    Unless(usePostgres, func(u *User) {\n        u.CreatedAt = time.Time{} // Let SQLite handle it\n    })\n\n// Feature flags\nenableNewFeature := featureFlags.IsEnabled(\"new_feature\")\n\nfactory.\n    When(enableNewFeature, func(u *User) {\n        u.NewField = \"enabled\"\n    }).\n    Unless(enableNewFeature, func(u *User) {\n        u.NewField = \"\"\n    })\n```\n\n## Clone() - Factory Variations\n\nCreate factory variations without affecting the original:\n\n```go\n// Base factory\nbaseFactory := factory.New(func(seq int64) User {\n    return User{\n        Name:  fmt.Sprintf(\"User %d\", seq),\n        Email: fmt.Sprintf(\"user%d@example.com\", seq),\n        Role:  \"user\",\n    }\n}).WithDefaults(func(u *User) {\n    u.Active = true\n})\n\n// Create variations\nadminFactory := baseFactory.Clone().WithTraits(func(u *User) {\n    u.Role = \"admin\"\n})\n\nmoderatorFactory := baseFactory.Clone().WithTraits(func(u *User) {\n    u.Role = \"moderator\"\n})\n\ntestFactory := baseFactory.Clone().WithTraits(func(u *User) {\n    u.Email = \"test@example.com\"\n})\n\n// Each factory is independent\nregularUser := baseFactory.Make()     // Role: \"user\"\nadmin := adminFactory.Make()          // Role: \"admin\"\nmoderator := moderatorFactory.Make()  // Role: \"moderator\"\ntestUser := testFactory.Make()        // Email: \"test@example.com\"\n```\n\n**Key features:**\n- Deep copy of all traits, states, and hooks\n- Sequence counter is reset for each clone\n- Original factory remains unchanged\n- Perfect for creating test variations\n\n## Lifecycle Hooks\n\n### Before Create Hooks\n\nRun logic before persistence (e.g., validation, setup):\n\n```go\nuserFactory := factory.New(func(seq int64) User {\n    return User{Name: fmt.Sprintf(\"User %d\", seq)}\n}).WithPersist(persistFn).\n  BeforeCreate(func(ctx context.Context, u *User) error {\n    // Validate before saving\n    if u.Email == \"\" {\n        return errors.New(\"email is required\")\n    }\n    return nil\n}).BeforeCreate(func(ctx context.Context, u *User) error {\n    // Set computed fields\n    u.Slug = slugify(u.Name)\n    return nil\n})\n```\n\nIf any `BeforeCreate` hook returns an error, persistence is skipped and the error is returned.\n\n### After Create Hooks\n\nRun logic after persistence (e.g., creating related records):\n\n```go\nuserFactory := factory.New(func(seq int64) User {\n    return User{Name: fmt.Sprintf(\"User %d\", seq)}\n}).WithPersist(persistFn).\n  AfterCreate(func(ctx context.Context, u *User) error {\n    // Create a profile for this user\n    return db.CreateProfile(ctx, u.ID)\n}).AfterCreate(func(ctx context.Context, u *User) error {\n    // Send welcome email\n    return emailService.SendWelcome(ctx, u.Email)\n})\n```\n\n### Hook Execution Order\n\nWhen calling `Create()` (hooks run **synchronously** per item):\n1. `Make()` - Build object with traits\n2. **BeforeCreate hooks** - Run in order (can return error)\n3. **Persist** - Save to database\n4. **AfterCreate hooks** - Run in order (can return error)\n\n**Hook Contracts:**\n- ✅ **BeforeCreate** - May mutate object; returning error aborts persistence\n- ✅ **Persist** - Should be idempotent if your tests may retry on transient failures\n- ✅ **AfterCreate** - Runs only if persist succeeds; errors bubble up to caller\n- ✅ **Execution order** - Multiple hooks run in registration order\n\n## Complete Example with Faker and Named States\n\n```go\npackage main\n\nimport (\n    \"context\"\n    \"fmt\"\n    \"time\"\n    \n    \"github.com/b3ndoi/factory-go/factory\"\n    \"github.com/brianvoe/gofakeit/v6\"\n)\n\ntype User struct {\n    ID              string\n    FirstName       string\n    LastName        string\n    Email           string\n    Role            string\n    Active          bool\n    EmailVerifiedAt *time.Time\n}\n\nfunc main() {\n    userFactory := factory.New(func(seq int64) User {\n        return User{}\n    }).WithDefaults(func(u *User) {\n        // Generate realistic fake data\n        u.FirstName = gofakeit.FirstName()\n        u.LastName = gofakeit.LastName()\n        u.Email = gofakeit.Email()\n        u.Role = \"user\"\n        u.Active = true\n    }).DefineState(\"admin\", func(u *User) {\n        u.Role = \"admin\"\n    }).DefineState(\"moderator\", func(u *User) {\n        u.Role = \"moderator\"\n    }).DefineState(\"verified\", func(u *User) {\n        now := time.Now()\n        u.EmailVerifiedAt = \u0026now\n    }).DefineState(\"inactive\", func(u *User) {\n        u.Active = false\n    }).WithPersist(func(ctx context.Context, u *User) (*User, error) {\n        u.ID = gofakeit.UUID()\n        // db.Insert(ctx, u)\n        return u, nil\n    })\n\n    ctx := context.Background()\n\n    // Create 10 regular verified users\n    users, _ := userFactory.State(\"verified\").CreateMany(ctx, 10)\n    \n    // Create 5 verified admins (chain multiple states!)\n    admins, _ := userFactory.State(\"admin\").State(\"verified\").CreateMany(ctx, 5)\n    \n    // Create 3 inactive moderators\n    mods, _ := userFactory.State(\"moderator\").State(\"inactive\").CreateMany(ctx, 3)\n    \n    // Create custom user with state + per-call override\n    special, _ := userFactory.State(\"admin\").Create(ctx, func(u *User) {\n        u.FirstName = \"Special\"\n        u.LastName = \"Admin\"\n    })\n    \n    fmt.Printf(\"Created %d users, %d admins, %d moderators, 1 special admin\\n\", \n        len(users), len(admins), len(mods))\n    fmt.Printf(\"Special admin: %s %s (%s)\\n\", \n        special.FirstName, special.LastName, special.Role)\n}\n```\n\n## API Reference\n\n### Factory Methods\n\n#### Setup Methods\n- `New(makeFn)` - Create a new factory with a base make function\n- `WithDefaults(...traits)` - Set default traits (applied first, ideal for faker)\n- `WithRawDefaults(...traits)` - Set traits applied ONLY for Raw/RawJSON methods\n- `WithTraits(...traits)` - Add global traits (applied after defaults)\n- `Sequence(...traits)` - Set traits that cycle through for each item created\n- `DefineState(name, trait)` - Register a named state for reusable configurations\n- `WithPersist(persistFn)` - Set persistence function (required for Create methods)\n- `BeforeCreate(hookFn)` - Add hooks that run before persistence\n- `AfterCreate(hookFn)` - Add hooks that run after persistence\n- `Tap(fn func(T))` - Set function to inspect/log each created item\n- `When(condition, ...traits)` - Apply traits only if condition is true\n- `Unless(condition, ...traits)` - Apply traits only if condition is false\n- `Clone()` - Create deep copy of factory with reset sequence\n\n#### State Application\n- `State(name)` - Apply a named state (returns new factory instance with state applied)\n\n#### Fluent API\n- `Count(n)` - Set count for fluent API (returns `CountedFactory`)\n- `Times(n)` - Alias for `Count()`\n\n#### Creation Methods (Single Item)\n- `Make(...traits)` - Build object in-memory without persisting\n- `Raw(...traits)` - Build with rawDefaults applied (for API testing)\n- `RawJSON(...traits)` - Build and marshal to JSON\n- `Create(ctx, ...traits)` - Build, persist, and run hooks for one object\n\n#### Creation Methods (Multiple Items)\n- `MakeMany(count, ...traits)` - Build multiple objects in-memory\n- `RawMany(count, ...traits)` - Build multiple with rawDefaults applied\n- `RawManyJSON(count, ...traits)` - Build multiple and marshal to JSON array\n- `CreateMany(ctx, count, ...traits)` - Build, persist, and run hooks for multiple objects\n\n#### Must* Variants (Panic on Error)\n- `MustCreate(ctx, ...traits)` - Create and panic on error\n- `MustCreateMany(ctx, count, ...traits)` - Create many and panic on error\n- `MustRawJSON(...traits)` - Get JSON and panic on marshal error\n- `MustRawManyJSON(count, ...traits)` - Get JSON array and panic on marshal error\n\n#### Relationship Helpers\n- `For[T, R](factory, relatedFactory, linkFn)` - Belongs-to: Each child gets its own parent\n- `ForModel[T, R](factory, relatedModel, linkFn)` - Belongs-to: All children share same parent\n- `Recycle[T, R](factory, relatedModel, linkFn)` - Alias for ForModel (semantic naming)\n- `Has[T, R](parentFactory, childFactory, count, linkFn)` - Has-many: Create parent with children\n- `HasAttached[T, R, P](parent, related, pivot, count, linkFn)` - Many-to-many: Parent with children + pivot\n\n#### Utility Methods\n- `ResetSequence()` - Reset sequence counter to 0 (useful for test isolation)\n- `Clone()` - Create deep copy of factory (resets sequence)\n\n### CountedFactory Methods\n\nReturned by `Count()` or `Times()`:\n\n- `Make(...traits) []T` - Build count items in-memory\n- `Create(ctx, ...traits) ([]*T, error)` - Build, persist, and run hooks for count items\n- `MustCreate(ctx, ...traits) []*T` - Create count items and panic on error\n- `Raw(...traits) []T` - Build count items with rawDefaults applied\n- `RawJSON(...traits) ([]byte, error)` - Build count items and marshal to JSON array\n- `MustRawJSON(...traits) []byte` - Get JSON array and panic on marshal error\n- `State(name) *CountedFactory[T]` - Apply a named state (chainable)\n\n### HasFactory Methods\n\nReturned by `Has()`:\n\n- `Make() (T, []R)` - Build parent with children in-memory\n- `Create(ctx) (*T, []*R, error)` - Create and persist parent with children\n- `MustCreate(ctx) (*T, []*R)` - Create parent with children, panic on error\n\n### HasAttachedFactory Methods\n\nReturned by `HasAttached()`:\n\n- `Make() (T, []R, []P)` - Build parent with related models and pivots in-memory\n- `Create(ctx) (*T, []*R, []*P, error)` - Create and persist parent, related models, and pivot records\n- `MustCreate(ctx) (*T, []*R, []*P)` - Create parent, related, and pivots, panic on error\n\n### Type Definitions\n\n```go\ntype Trait[T any] func(*T)\ntype BeforeCreate[T any] func(ctx context.Context, t *T) error\ntype AfterCreate[T any] func(ctx context.Context, t *T) error\ntype PersistFn[T any] func(ctx context.Context, t *T) (*T, error)\n```\n\n## Comparison with Laravel\n\n| Laravel | Factory-Go |\n|---------|------------|\n| `User::factory()-\u003emake()` | `userFactory.Make()` |\n| `User::factory()-\u003eraw()` | `userFactory.Raw()` |\n| `User::factory()-\u003ecount(10)-\u003emake()` | `userFactory.Count(10).Make()` or `MakeMany(10)` |\n| `User::factory()-\u003ecreate()` | `userFactory.Create(ctx)` |\n| `User::factory()-\u003ecount(10)-\u003ecreate()` | `userFactory.Count(10).Create(ctx)` or `CreateMany(ctx, 10)` |\n| `User::factory()-\u003eadmin()-\u003ecreate()` | `userFactory.State(\"admin\").Create(ctx)` |\n| `User::factory()-\u003ecount(5)-\u003eadmin()-\u003ecreate()` | `userFactory.Count(5).State(\"admin\").Create(ctx)` |\n| `User::factory()-\u003esequence(...)-\u003ecreate()` | `userFactory.Sequence(...).Create(ctx)` |\n| `Post::factory()-\u003efor(User::factory())-\u003ecreate()` | `factory.For(postFactory, userFactory, linkFn).Create(ctx)` |\n| `Post::factory()-\u003efor($user)-\u003ecreate()` | `factory.ForModel(postFactory, user, linkFn).Create(ctx)` |\n| `Post::factory()-\u003erecycle($user)-\u003ecreate()` | `factory.Recycle(postFactory, user, linkFn).Create(ctx)` |\n| `User::factory()-\u003ehas(Post::factory()-\u003ecount(3))` | `factory.Has(userFactory, postFactory, 3, linkFn).Create(ctx)` |\n| `User::factory()-\u003ehasAttached(Role::factory(), pivot)` | `factory.HasAttached(userFactory, roleFactory, pivotFactory, 3, linkFn)` |\n| `definition()` | `WithDefaults()` |\n| `configure()` | `WithTraits()` |\n| `public function admin() { return $this-\u003estate(...); }` | `DefineState(\"admin\", trait)` |\n| `beforeCreating()` | `BeforeCreate()` |\n| `afterCreating()` | `AfterCreate()` |\n| *(No equivalent)* | `ResetSequence()`, `Clone()`, `Tap()`, `When()`, `Unless()`, `Must*` |\n\n## License\n\nMIT License - See LICENSE file for details\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fb3ndoi%2Ffactory-go","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fb3ndoi%2Ffactory-go","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fb3ndoi%2Ffactory-go/lists"}