{"id":18621275,"url":"https://github.com/yiling-j/carrier","last_synced_at":"2025-04-11T02:31:41.259Z","repository":{"id":43692434,"uuid":"433508816","full_name":"Yiling-J/carrier","owner":"Yiling-J","description":"🛠 A test fixtures replacement for Go, support struct and ent, inspired by factory_bot/factory_boy","archived":false,"fork":false,"pushed_at":"2023-02-25T10:05:46.000Z","size":361,"stargazers_count":6,"open_issues_count":1,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-25T08:22:49.874Z","etag":null,"topics":["code-generation","factory","fixtures","testing"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Yiling-J.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"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}},"created_at":"2021-11-30T16:43:53.000Z","updated_at":"2022-01-23T06:02:06.000Z","dependencies_parsed_at":"2024-06-19T06:12:15.780Z","dependency_job_id":"af0d5dca-778c-4136-96eb-30e7568c90e6","html_url":"https://github.com/Yiling-J/carrier","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Yiling-J%2Fcarrier","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Yiling-J%2Fcarrier/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Yiling-J%2Fcarrier/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Yiling-J%2Fcarrier/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Yiling-J","download_url":"https://codeload.github.com/Yiling-J/carrier/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248329598,"owners_count":21085565,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["code-generation","factory","fixtures","testing"],"created_at":"2024-11-07T04:10:08.262Z","updated_at":"2025-04-11T02:31:36.249Z","avatar_url":"https://github.com/Yiling-J.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# carrier - A Test Fixtures Replacement for Go\n![example workflow](https://github.com/Yiling-J/carrier/actions/workflows/go.yml/badge.svg)\n![Go Report Card](https://goreportcard.com/badge/github.com/Yiling-J/carrier?style=flat-square)\n\n- **Statically Typed** - 100% statically typed using code generation\n- **Developer Friendly API** - explicit API with method chaining support\n- **Feature Rich** - Default/Sequence/SubFactory/PostHook/Trait\n- **Ent Support** - [ent: An Entity Framework For Go](https://github.com/ent/ent)\n\nA snippet show how carrier works:\n\n- *You have a model*\n```go\ntype User struct {\n\tName  string\n\tEmail string\n\tGroup *Group\n}\n\n```\n- *Add carrier schema*\n```go\nSchemas := []carrier.Schema{\n\t\u0026carrier.StructSchema{\n\t\tTo: model.User{},\n\t},\n}\n```\n- *Generate Structs* 🎉\n```go\nuserMetaFactory := carrier.UserMetaFactory()\nuserFactory := userMetaFactory.\n\tSetNameDefault(\"carrier\").\n\tSetEmailLazy(func(ctx context.Context, i *model.User) (string, error) {\n\t\treturn fmt.Sprintf(\"%s@carrier.go\", i.Name), nil\n\t}).\n\tSetGroupFactory(groupFactory.Create).\n\tBuild()\nuser, err := userFactory.Create(ctx)\nusers, err := userFactory.CreateBatch(ctx, 5)\n```\n\n## Installation\n```console\ngo get github.com/Yiling-J/carrier/cmd\n```\nAfter installing `carrier` codegen, go to the root directory(or the directory you think carrier should stay) of your project, and run:\n```console\ngo run github.com/Yiling-J/carrier/cmd init\n```\nThe command above will generate `carrier` directory under current directory:\n```console {12-20}\n└── carrier\n    └── schema\n        └── schema.go\n```\nIt's up to you where the carrier directory should be, just remember to use the right directory in **MetaFactory Generation** step.\n\n## Add Schema\nEdit `schema.go` and add some schemas:\n\n**\u003e struct**\n```go\npackage schema\n\nimport (\n\t\"github.com/Yiling-J/carrier\"\n)\n\nvar (\n\tSchemas = []carrier.Schema{\n\t\t\u0026carrier.StructSchema{\n\t\t\tTo: model.User{},\n\t\t},\n\t}\n)\n```\n\n**\u003e ent**\n\nTo support ent, you need to provide the `ent.{Name}Create` struct to schema, so carrier can get enough information.\n```go\npackage schema\n\nimport (\n\t\"github.com/Yiling-J/carrier\"\n\t\"your/ent\"\n)\n\nvar (\n\tSchemas = []carrier.Schema{\n\t\t\u0026carrier.EntSchema{\n\t\t\tTo: \u0026ent.UserCreate{},\n\t\t},\n\t}\n)\n```\n\nThe `To` field only accept struct/struct pointer, carrier will valid that on generation step. [Schema definition reference](#schema-definition)\n\n## MetaFactory Generation\nRun code generation from the root directory of the project as follows:\n```console\n# this will use default schema path ./carrier/schema\ngo run github.com/Yiling-J/carrier/cmd generate\n```\nOr can use custom schema path:\n```console\ngo run github.com/Yiling-J/carrier/cmd generate ./your/carrier/schema\n```\n\nThis produces the following files:\n```console {12-20}\n└── carrier\n    ├── factory\n    │   ├── base.go\n    │   ├── ent_user.go\n    │   └── user.go\n    ├── schema\n    │   └── schema.go\n    └── factory.go\n```\nHere `factory.go` include all meta factories you need.\nAlso all ent files and meta factories will have `ent` prefix to avoid name conflict.\n\nIf you update schemas, just run `generate` again.\n\n## Build Factory and Generate Structs\nTo construct a real factory for testing:\n\n**Create MetaFactory struct**\n```go\nuserMetaFactory := carrier.UserMetaFactory()\n```\n**Build factory from meta factory**\n```go\nuserFactory := userMetaFactory.SetNameDefault(\"carrier\").Build()\n```\nMetaFactory provide several methods to help you initial field values automatically.\n\n[MetaFactory API Reference](#metafactory-api)\n\n**Create structs**\n\n**\u003e struct**\n```go\nuser, err := userFactory.Create(context.TODO())\nusers, err := userFactory.CreateBatch(context.TODO(), 3)\n```\n**\u003e ent**\n```go\n// need ent client\nuser, err := userFactory.Client(entClient).Create(context.TODO())\nuser, err := userFactory.Client(entClient).CreateBatch(context.TODO(), 3)\n```\n[Factory API Reference](#factory-api)\n\n**Use factory wrapper**\n\nCarrier also include a wrapper where you can put all your factories in:\n\n**\u003e struct**\n```go\nfactory := carrier.NewFactory()\nfactory.SetUserFactory(userFactory)\nfactory.UserFactory().Create(context.TODO())\n```\n**\u003e ent**\n```go\nfactory := carrier.NewEntFactory(client)\n// this step will assign factory client to userFactory also\nfactory.SetUserFactory(userFactory)\nfactory.UserFactory().Create(context.TODO())\n// access ent client\nclient := factory.Client()\n```\n## Schema Definition\nThere are 2 kinds of schemas `StructSchema` and `EntSchema`,\nboth of them implement `carrier.Schema` interface so you can put them in the schema slice.\n\nEach schema has 4 fields:\n- **Alias**: Optional. If you have 2 struct type from different package, but have same name, add alias for them.\nCarrier will use alias directly as factory name.\n\n- **To**: Required. For `StructSchema`, this is the struct factory should generate. Carrier will get struct type from it and used in code generation, Only public fields are concerned.\nFor `EntSchema`, this field should be the `{SchemaName}Create` struct which `ent` generated. Carrier will look up all `Set{Field}` methods\nand generate factory based on them. Both struct and pointer of struct are OK.\n\n- **Traits**: Optional. String slice of trait names. Traits allow you to group attributes together and override them at once.\n\n- **Posts**: Optional. Slice of `carrier.PostField`. Each `carrier.PostField` require `Name`(string) and `Input`(any interface{}), and map to a post function after code generation.\nPost function will run after struct created, with input value as param.\n\n## MetaFactory API\nMetaFactory API can be categorized into 8 types of method:\n- Each field in `To` struct has 4 types:\n\t- **Default**: `Set{Field}Default`\n\t- **Sequence**: `Set{Field}Sequence`\n\t- **Lazy**: `Set{Field}Lazy`\n\t- **Factory**: `Set{Field}Factory`\n\n- Each field in `[]Posts` has 1 type:\n\t- **Post**: `Set{PostField}PostFunc`\n\n- Each name in `[]Traits` has 1 type:\n\t- **Trait**: `Set{TraitName}Trait`\n\n- Each `MetaFactory` has 2 type:\n    - **BeforeCreate**: `SetBeforeCreateFunc`\n\t- **AfterCreate**: `SetAfterCreateFunc`\n\nThe evaluation order of these methods are:\n```\nTrait -\u003e Default/Sequence/Factory -\u003e Lazy -\u003e BeforeCreate -\u003e Create -\u003e AfterCreate -\u003e Post\n```\n`Create` only exists in ent factory, will call ent builder `Save` method.\n\nPut `Trait` first because `Trait` can override other types.\n\nAll methods except `Default` and `Trait` use function as input and it's fine to set it to `nil`. This is very useful in `Trait` override.\n\n#### Default\nSet a fixed default value for field.\n```go\nuserMetaFactory.SetNameDefault(\"carrier\")\n```\n\n#### Sequence\nIf a field should be unique, and thus different for all built structs, use a sequence.\nSequence counter is shared by all fields in a factory, not a single field.\n```go\n// i is the current sequence counter\nuserMetaFactory.SetNameSequence(\n\tfunc(ctx context.Context, i int) (string, error) {\n\t\treturn fmt.Sprintf(\"user_%d\", i), nil\n\t},\n),\n```\nThe sequence counter is concurrent safe and increase by 1 each time factory's `Create` method called.\n\n#### Lazy\nFor fields whose value is computed from other fields, use lazy attribute. Only Default/Sequence/Factory values are accessible in the struct.\n```go\nuserMetaFactory.SetEmailLazy(\n\tfunc(ctx context.Context, i *model.User) (string, error) {\n\t\treturn fmt.Sprintf(\"%s@carrier.go\", i.Name), nil\n\t},\n)\n```\n**\u003e ent**\n\nEnt is a little different because the struct is created after `Save`. And carrier call ent's `Set{Field}` method to set values.\nSo the input param here is not `*model.User`, but a temp containter struct created by carrier, hold all fields you can set.\n```go\nentUserMetaFactory.SetEmailLazy(\n\tfunc(ctx context.Context, i *factory.EntUserMutator) (string, error) {\n\t\treturn fmt.Sprintf(\"%s@carrier.com\", i.Name), nil\n\t},\n)\n```\nYou can get original ent mutation using `i.EntCreator()`:\n```go\nentUserMetaFactory.SetEmailLazy(\n\tfunc(ctx context.Context, i *factory.EntUserMutator) (string, error) {\n\t\ti.EntCreator().SetName(\"apple\")\n\t\treturn \"apple@carrier.com\", nil\n\t},\n)\n```\n\n#### Factory\nIf a field's value has related factory, use `relatedFactory.Create` method as param here. You can also set the function manually.\n```go\n// User struct has a Group field, type is Group\nuserMetaFactory.SetGroupFactory(groupFactory.Create)\n```\n\n**\u003e ent**\n\nMake sure related factory's ent client is set. By using factory wrapper or set it explicitly.\n\n#### BeforeCreate\nFor struct factory, before create function is called after all lazy functions done. For ent factory, before create function is called right before to ent's `Save` method.\n```go\ngroupMetaFactory.SetBeforeCreateFunc(func(ctx context.Context, i *model.User) error {\n\treturn nil\n})\n```\n**\u003e ent**\n\n```go\nentUserMetaFactory.SetBeforeCreateFunc(func(ctx context.Context, i *factory.EntUserMutator) error {\n\treturn nil\n})\n\n```\n\n#### AfterCreate\nFor struct factory, after create function is called after all lazy functions done. For ent factory, after create function is called next to ent's `Save` method.\n```go\nuserMetaFactory.SetAfterCreateFunc(func(ctx context.Context, i *model.User) error {\n\tfmt.Printf(\"user: %d saved\", i.Name)\n\treturn nil\n})\n```\n\n#### Post\nPost functions will run once `AfterCreate` step done.\n```go\n// user MetaFactory\nuserMetaFactory.SetWelcomePostFunc(\n\tfunc(ctx context.Context, set bool, obj *model.User, i string) error {\n\t\tif set {\n\t\t\tmessage.SendTo(obj, i)\n\t\t}\n\t\treturn nil\n\t},\n)\n// user Factory, send welcome message\nuserFactory.SetWelcomePost(\"welcome to carrier\").Create(context.TODO())\n// user Factory, no welcome message\nuserFactory.Create(context.TODO())\n```\n\n#### Trait\nTrait is used to override some fields at once, activated by `With{Name}Trait` method.\n```go\n// override name\nuserMetaFactory.SetGopherTrait(factory.UserTrait().SetNameDefault(\"gopher\"))\n// user Factory\nuserFactory.WithGopherTrait().Create(context.TODO())\n```\nThe `Trait` struct share same API with `MetaFactory` except `Set{Name}Trait` one, that means you can override 6 methods within a trait.\n`Trait` only override methods you explicitly set, the exampe above will only override name field. So you can combine multiple traits together,\neach change some parts of the struct. If multiple traits override same field, the last one will win:\n```go\nuserMetaFactory.SetGopherTrait(factory.UserTrait().SetNameDefault(\"gopher\")).\nSetFooTrait(factory.UserTrait().SetNameDefault(\"foo\"))\n// user name is foo\nuserFactory.WithGopherTrait().WithFooTrait().Create(context.TODO())\n// user name is gopher\nuserFactory.WithFooTrait().WithGopherTrait().Create(context.TODO())\n```\n#### Build\nThis is the final step for `MetaFactory` definition, call this method will return a `Factory` which you can use to create structs.\n\n## Factory API\nFactory API provide 3 types of method, `Set{Field}` to override some field, `Set{Field}Post` to call post function and `With{Name}Trait` to enable trait.\n#### Set\nOverride field value. This method has the highest priority and will override your field method in `MetaFactory`.\n```go\nuserFactory.SetName(\"foo\").Create(context.TODO())\n```\n#### SetPost\nCall post function defined in `MetaFactory` with param.\n```go\n// create a user with 3 friends\nuserFactory.SetFriendsPost(3).Create(context.TODO())\n```\n#### WithTrait\nEnable a named trait. If you enable multi traits, and traits have overlapping, the latter one will override the former.\n```go\nuserFactory.WithFooTrait().WithBarTrait().Create(context.TODO())\n```\n#### Create\nCreate pointer of struct.\n#### CreateV\nCreate struct.\n#### CreateBatch\nCreate slice of struct pointer.\n#### CreateBatchV\nCreate slice of struct.\n\n## Common Recipes\n- [Recipe Example](examples/recipe/main.go)\n- [Ent Recipe Example](examples/ent_recipe/main.go)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyiling-j%2Fcarrier","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyiling-j%2Fcarrier","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyiling-j%2Fcarrier/lists"}