├── go.mod ├── .github └── workflows │ └── test.yml ├── examples ├── create_with_option.go ├── simple_factory.go ├── factory_with_randomdata.go ├── recursive_factory.go ├── subfactory_slice.go ├── subfactory.go ├── refer_to_parent_factory.go ├── gorm_integration.go └── go_pg_integration.go ├── LICENSE ├── factory ├── utils.go ├── factory.go └── factory_test.go └── README.md /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/bluele/factory-go 2 | 3 | go 1.15 4 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: [push] 3 | jobs: 4 | test: 5 | name: Test 6 | runs-on: ubuntu-latest 7 | steps: 8 | 9 | - name: Set up Go 1.15 10 | uses: actions/setup-go@v1 11 | with: 12 | go-version: 1.15 13 | id: go 14 | 15 | - name: Check out code into the Go module directory 16 | uses: actions/checkout@v2 17 | 18 | - name: go test 19 | run: go test -v ./factory/... 20 | -------------------------------------------------------------------------------- /examples/create_with_option.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/bluele/factory-go/factory" 6 | ) 7 | 8 | type Group struct { 9 | ID int 10 | } 11 | 12 | type User struct { 13 | ID int 14 | Groups []*Group 15 | } 16 | 17 | var UserFactory = factory.NewFactory( 18 | &User{}, 19 | ).SeqInt("ID", func(n int) (interface{}, error) { 20 | return n, nil 21 | }) 22 | 23 | func main() { 24 | for i := 1; i <= 3; i++ { 25 | user := UserFactory.MustCreateWithOption(map[string]interface{}{ 26 | "Groups": []*Group{ 27 | &Group{i}, &Group{i + 1}, 28 | }, 29 | }).(*User) 30 | fmt.Println("ID:", user.ID) 31 | for _, group := range user.Groups { 32 | fmt.Println(" Group.ID:", group.ID) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/simple_factory.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/bluele/factory-go/factory" 6 | ) 7 | 8 | type User struct { 9 | ID int 10 | Name string 11 | Location string 12 | } 13 | 14 | // 'Location: "Tokyo"' is default value. 15 | var UserFactory = factory.NewFactory( 16 | &User{Location: "Tokyo"}, 17 | ).SeqInt("ID", func(n int) (interface{}, error) { 18 | return n, nil 19 | }).Attr("Name", func(args factory.Args) (interface{}, error) { 20 | user := args.Instance().(*User) 21 | return fmt.Sprintf("user-%d", user.ID), nil 22 | }) 23 | 24 | func main() { 25 | for i := 0; i < 3; i++ { 26 | user := UserFactory.MustCreate().(*User) 27 | fmt.Println("ID:", user.ID, " Name:", user.Name, " Location:", user.Location) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/factory_with_randomdata.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/Pallinder/go-randomdata" 6 | "github.com/bluele/factory-go/factory" 7 | ) 8 | 9 | type User struct { 10 | ID int 11 | Name string 12 | Location string 13 | } 14 | 15 | var UserFactory = factory.NewFactory( 16 | &User{}, 17 | ).SeqInt("ID", func(n int) (interface{}, error) { 18 | return n, nil 19 | }).Attr("Name", func(args factory.Args) (interface{}, error) { 20 | return randomdata.FullName(randomdata.RandomGender), nil 21 | }).Attr("Location", func(args factory.Args) (interface{}, error) { 22 | return randomdata.City(), nil 23 | }) 24 | 25 | func main() { 26 | for i := 0; i < 3; i++ { 27 | user := UserFactory.MustCreate().(*User) 28 | fmt.Println("ID:", user.ID, " Name:", user.Name, " Location:", user.Location) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/recursive_factory.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/Pallinder/go-randomdata" 6 | "github.com/bluele/factory-go/factory" 7 | ) 8 | 9 | type User struct { 10 | ID int 11 | Name string 12 | CloseFriend *User 13 | } 14 | 15 | var UserFactory = factory.NewFactory( 16 | &User{}, 17 | ) 18 | 19 | func init() { 20 | UserFactory.SeqInt("ID", func(n int) (interface{}, error) { 21 | return n, nil 22 | }).Attr("Name", func(args factory.Args) (interface{}, error) { 23 | return randomdata.FullName(randomdata.RandomGender), nil 24 | }).SubRecursiveFactory("CloseFriend", UserFactory, func() int { return 2 }) // recursive depth is always 2 25 | } 26 | 27 | func main() { 28 | user := UserFactory.MustCreate().(*User) 29 | fmt.Println("ID:", user.ID, " Name:", user.Name, 30 | " CloseFriend.ID:", user.CloseFriend.ID, " CloseFriend.Name:", user.CloseFriend.Name) 31 | // `user.CloseFriend.CloseFriend.CloseFriend ` depth is 3, so this value is always nil. 32 | fmt.Printf("%v %v\n", user.CloseFriend.CloseFriend, user.CloseFriend.CloseFriend.CloseFriend) 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Jun Kimura 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/subfactory_slice.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/bluele/factory-go/factory" 6 | ) 7 | 8 | type Post struct { 9 | ID int 10 | Content string 11 | } 12 | 13 | type User struct { 14 | ID int 15 | Name string 16 | Posts []*Post 17 | } 18 | 19 | var PostFactory = factory.NewFactory( 20 | &Post{}, 21 | ).SeqInt("ID", func(n int) (interface{}, error) { 22 | return n, nil 23 | }).Attr("Content", func(args factory.Args) (interface{}, error) { 24 | post := args.Instance().(*Post) 25 | return fmt.Sprintf("post-%d", post.ID), nil 26 | }) 27 | 28 | var UserFactory = factory.NewFactory( 29 | &User{}, 30 | ).SeqInt("ID", func(n int) (interface{}, error) { 31 | return n, nil 32 | }).Attr("Name", func(args factory.Args) (interface{}, error) { 33 | user := args.Instance().(*User) 34 | return fmt.Sprintf("user-%d", user.ID), nil 35 | }).SubSliceFactory("Posts", PostFactory, func() int { return 3 }) 36 | 37 | func main() { 38 | for i := 0; i < 3; i++ { 39 | user := UserFactory.MustCreate().(*User) 40 | fmt.Println("ID:", user.ID, " Name:", user.Name) 41 | for _, post := range user.Posts { 42 | fmt.Printf("\tPost.ID: %v Post.Content: %v\n", post.ID, post.Content) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /examples/subfactory.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/bluele/factory-go/factory" 6 | ) 7 | 8 | type Group struct { 9 | ID int 10 | Name string 11 | } 12 | 13 | type User struct { 14 | ID int 15 | Name string 16 | Location string 17 | Group *Group 18 | } 19 | 20 | var GroupFactory = factory.NewFactory( 21 | &Group{}, 22 | ).SeqInt("ID", func(n int) (interface{}, error) { 23 | return 2 - n%2, nil 24 | }).Attr("Name", func(args factory.Args) (interface{}, error) { 25 | group := args.Instance().(*Group) 26 | return fmt.Sprintf("group-%d", group.ID), nil 27 | }) 28 | 29 | // 'Location: "Tokyo"' is default value. 30 | var UserFactory = factory.NewFactory( 31 | &User{Location: "Tokyo"}, 32 | ).SeqInt("ID", func(n int) (interface{}, error) { 33 | return n, nil 34 | }).Attr("Name", func(args factory.Args) (interface{}, error) { 35 | user := args.Instance().(*User) 36 | return fmt.Sprintf("user-%d", user.ID), nil 37 | }).SubFactory("Group", GroupFactory) 38 | 39 | func main() { 40 | for i := 0; i < 3; i++ { 41 | user := UserFactory.MustCreate().(*User) 42 | fmt.Println( 43 | "ID:", user.ID, " Name:", user.Name, " Location:", user.Location, 44 | " Group.ID:", user.Group.ID, " Group.Name", user.Group.Name) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /examples/refer_to_parent_factory.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/bluele/factory-go/factory" 6 | ) 7 | 8 | type User struct { 9 | ID int 10 | Name string 11 | Group *Group 12 | } 13 | 14 | type Group struct { 15 | ID int 16 | Name string 17 | Users []*User 18 | } 19 | 20 | var UserFactory = factory.NewFactory( 21 | &User{}, 22 | ).SeqInt("ID", func(n int) (interface{}, error) { 23 | return n, nil 24 | }).Attr("Name", func(args factory.Args) (interface{}, error) { 25 | user := args.Instance().(*User) 26 | return fmt.Sprintf("user-%d", user.ID), nil 27 | }).Attr("Group", func(args factory.Args) (interface{}, error) { 28 | if parent := args.Parent(); parent != nil { 29 | // if args have parent, use it. 30 | return parent.Instance(), nil 31 | } 32 | return nil, nil 33 | }) 34 | 35 | var GroupFactory = factory.NewFactory( 36 | &Group{}, 37 | ).SeqInt("ID", func(n int) (interface{}, error) { 38 | return 2 - n%2, nil 39 | }).Attr("Name", func(args factory.Args) (interface{}, error) { 40 | group := args.Instance().(*Group) 41 | return fmt.Sprintf("group-%d", group.ID), nil 42 | }).SubSliceFactory("Users", UserFactory, func() int { return 3 }) 43 | 44 | func main() { 45 | group := GroupFactory.MustCreate().(*Group) 46 | fmt.Println("Group.ID:", group.ID) 47 | for _, user := range group.Users { 48 | fmt.Println("\tUser.ID:", user.ID, " User.Name:", user.Name, " User.Group.ID:", user.Group.ID) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /factory/utils.go: -------------------------------------------------------------------------------- 1 | package factory 2 | 3 | import ( 4 | "reflect" 5 | "strings" 6 | ) 7 | 8 | func getAttrName(sf reflect.StructField, tagName string) string { 9 | tag := sf.Tag.Get(tagName) 10 | if tag != "" { 11 | return tag 12 | } 13 | return sf.Name 14 | } 15 | 16 | func setValueWithAttrPath(inst *reflect.Value, tp reflect.Type, attr string, v interface{}) bool { 17 | attrs := strings.Split(attr, ".") 18 | if len(attrs) <= 1 { 19 | return false 20 | } 21 | current := inst 22 | currentTp := tp 23 | isSet := true 24 | for _, attr := range attrs { 25 | rt, rv := indirectPtrValue(currentTp, *current) 26 | currentTp = rt 27 | current = &rv 28 | 29 | if currentTp.Kind() != reflect.Struct { 30 | isSet = false 31 | break 32 | } 33 | ftp, ok := currentTp.FieldByName(attr) 34 | if !ok { 35 | isSet = false 36 | break 37 | } 38 | 39 | field := current.FieldByName(attr) 40 | if field == emptyValue { 41 | isSet = false 42 | break 43 | } 44 | 45 | if ftp.Type.Kind() == reflect.Ptr && field.IsNil() { 46 | field.Set(reflect.New(ftp.Type.Elem())) 47 | } 48 | current = &field 49 | currentTp = ftp.Type 50 | } 51 | if isSet { 52 | current.Set(reflect.ValueOf(v)) 53 | } 54 | return isSet 55 | } 56 | 57 | func indirectPtrValue(rt reflect.Type, rv reflect.Value) (reflect.Type, reflect.Value) { 58 | for rt.Kind() == reflect.Ptr { 59 | rt = rt.Elem() 60 | rv = rv.Elem() 61 | } 62 | return rt, rv 63 | } 64 | -------------------------------------------------------------------------------- /examples/gorm_integration.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/bluele/factory-go/factory" 8 | "github.com/jinzhu/gorm" 9 | _ "github.com/jinzhu/gorm/dialects/sqlite" 10 | ) 11 | 12 | type Group struct { 13 | ID int `gorm:"primary_key"` 14 | Name string 15 | } 16 | 17 | type User struct { 18 | ID int `gorm:"primary_key"` 19 | Name string 20 | Group *Group 21 | } 22 | 23 | var UserFactory = factory.NewFactory( 24 | &User{}, 25 | ).SeqInt("ID", func(n int) (interface{}, error) { 26 | return n, nil 27 | }).Attr("Name", func(args factory.Args) (interface{}, error) { 28 | user := args.Instance().(*User) 29 | return fmt.Sprintf("user-%d", user.ID), nil 30 | }).OnCreate(func(args factory.Args) error { 31 | db := args.Context().Value("db").(*gorm.DB) 32 | return db.Create(args.Instance()).Error 33 | }).SubFactory("Group", GroupFactory) 34 | 35 | var GroupFactory = factory.NewFactory( 36 | &Group{}, 37 | ).SeqInt("ID", func(n int) (interface{}, error) { 38 | return n, nil 39 | }).Attr("Name", func(args factory.Args) (interface{}, error) { 40 | group := args.Instance().(*Group) 41 | return fmt.Sprintf("group-%d", group.ID), nil 42 | }).OnCreate(func(args factory.Args) error { 43 | db := args.Context().Value("db").(*gorm.DB) 44 | return db.Create(args.Instance()).Error 45 | }) 46 | 47 | func main() { 48 | db, err := gorm.Open("sqlite3", "test.db") 49 | if err != nil { 50 | panic("failed to connect database") 51 | } 52 | db.LogMode(true) 53 | db.AutoMigrate(&Group{}, &User{}) 54 | 55 | for i := 0; i < 3; i++ { 56 | tx := db.Begin() 57 | ctx := context.WithValue(context.Background(), "db", tx) 58 | v, err := UserFactory.CreateWithContext(ctx) 59 | if err != nil { 60 | panic(err) 61 | } 62 | user := v.(*User) 63 | fmt.Println(user, *user.Group) 64 | tx.Commit() 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /examples/go_pg_integration.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/bluele/factory-go/factory" 8 | "github.com/go-pg/pg" 9 | "github.com/go-pg/pg/orm" 10 | ) 11 | 12 | type Group struct { 13 | ID int 14 | Name string 15 | } 16 | 17 | type User struct { 18 | ID int 19 | Name string 20 | Group *Group 21 | } 22 | 23 | var UserFactory = factory.NewFactory( 24 | &User{}, 25 | ).SeqInt("ID", func(n int) (interface{}, error) { 26 | return n, nil 27 | }).Attr("Name", func(args factory.Args) (interface{}, error) { 28 | user := args.Instance().(*User) 29 | return fmt.Sprintf("user-%d", user.ID), nil 30 | }).OnCreate(func(args factory.Args) error { 31 | tx := args.Context().Value("tx").(*pg.Tx) 32 | return tx.Insert(args.Instance()) 33 | }).SubFactory("Group", GroupFactory) 34 | 35 | var GroupFactory = factory.NewFactory( 36 | &Group{}, 37 | ).SeqInt("ID", func(n int) (interface{}, error) { 38 | return n, nil 39 | }).Attr("Name", func(args factory.Args) (interface{}, error) { 40 | group := args.Instance().(*Group) 41 | return fmt.Sprintf("group-%d", group.ID), nil 42 | }).OnCreate(func(args factory.Args) error { 43 | tx := args.Context().Value("tx").(*pg.Tx) 44 | return tx.Insert(args.Instance()) 45 | }) 46 | 47 | func createTestSchema(db *pg.DB) error { 48 | tables := []interface{}{ 49 | &Group{}, 50 | &User{}, 51 | } 52 | for _, table := range tables { 53 | err := db.DropTable(table, &orm.DropTableOptions{ 54 | IfExists: true, 55 | Cascade: true, 56 | }) 57 | if err != nil { 58 | return err 59 | } 60 | 61 | err = db.CreateTable(table, nil) 62 | if err != nil { 63 | return err 64 | } 65 | } 66 | return nil 67 | } 68 | 69 | func openDB() *pg.DB { 70 | db := pg.Connect(&pg.Options{ 71 | User: "postgres", 72 | }) 73 | 74 | err := createTestSchema(db) 75 | if err != nil { 76 | panic(err) 77 | } 78 | 79 | return db 80 | } 81 | 82 | func main() { 83 | db := openDB() 84 | for i := 0; i < 3; i++ { 85 | tx, err := db.Begin() 86 | if err != nil { 87 | panic(err) 88 | } 89 | 90 | ctx := context.WithValue(context.Background(), "tx", tx) 91 | v, err := UserFactory.CreateWithContext(ctx) 92 | if err != nil { 93 | panic(err) 94 | } 95 | user := v.(*User) 96 | fmt.Println(user, *user.Group) 97 | tx.Commit() 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # factory-go 2 | 3 | ![Test](https://github.com/bluele/factory-go/workflows/Test/badge.svg) 4 | [![GoDoc](https://godoc.org/github.com/bluele/factory-go?status.svg)](https://pkg.go.dev/github.com/bluele/factory-go?tab=doc) 5 | 6 | factory-go is a fixtures replacement inspired by [factory_boy](https://github.com/FactoryBoy/factory_boy) and [factory_bot](https://github.com/thoughtbot/factory_bot). 7 | 8 | It can be generated easily complex objects by using this, and maintain easily those objects generaters. 9 | 10 | ## Install 11 | 12 | ``` 13 | $ go get -u github.com/bluele/factory-go/factory 14 | ``` 15 | 16 | ## Usage 17 | 18 | All of the following code on [examples](https://github.com/bluele/factory-go/tree/master/examples). 19 | 20 | * [Define a simple factory](https://github.com/bluele/factory-go#define-a-simple-factory) 21 | * [Use factory with random yet realistic values](https://github.com/bluele/factory-go#use-factory-with-random-yet-realistic-values) 22 | * [Define a factory includes sub-factory](https://github.com/bluele/factory-go#define-a-factory-includes-sub-factory) 23 | * [Define a factory includes a slice for sub-factory](https://github.com/bluele/factory-go#define-a-factory-includes-a-slice-for-sub-factory) 24 | * [Define a factory includes sub-factory that contains self-reference](https://github.com/bluele/factory-go#define-a-factory-includes-sub-factory-that-contains-self-reference) 25 | * [Define a sub-factory refers to parent factory](https://github.com/bluele/factory-go#define-a-sub-factory-refers-to-parent-factory) 26 | 27 | ### Define a simple factory 28 | 29 | Declare an factory has a set of simple attribute, and generate a fixture object. 30 | 31 | ```go 32 | package main 33 | 34 | import ( 35 | "fmt" 36 | "github.com/bluele/factory-go/factory" 37 | ) 38 | 39 | type User struct { 40 | ID int 41 | Name string 42 | Location string 43 | } 44 | 45 | // 'Location: "Tokyo"' is default value. 46 | var UserFactory = factory.NewFactory( 47 | &User{Location: "Tokyo"}, 48 | ).SeqInt("ID", func(n int) (interface{}, error) { 49 | return n, nil 50 | }).Attr("Name", func(args factory.Args) (interface{}, error) { 51 | user := args.Instance().(*User) 52 | return fmt.Sprintf("user-%d", user.ID), nil 53 | }) 54 | 55 | func main() { 56 | for i := 0; i < 3; i++ { 57 | user := UserFactory.MustCreate().(*User) 58 | fmt.Println("ID:", user.ID, " Name:", user.Name, " Location:", user.Location) 59 | } 60 | } 61 | ``` 62 | 63 | Output: 64 | 65 | ``` 66 | ID: 1 Name: user-1 Location: Tokyo 67 | ID: 2 Name: user-2 Location: Tokyo 68 | ID: 3 Name: user-3 Location: Tokyo 69 | ``` 70 | 71 | ### Use factory with random yet realistic values. 72 | 73 | Tests look better with random yet realistic values. For example, you can use [go-randomdata](https://github.com/Pallinder/go-randomdata) library to get them: 74 | 75 | ```go 76 | package main 77 | 78 | import ( 79 | "fmt" 80 | "github.com/Pallinder/go-randomdata" 81 | "github.com/bluele/factory-go/factory" 82 | ) 83 | 84 | type User struct { 85 | ID int 86 | Name string 87 | Location string 88 | } 89 | 90 | // 'Location: "Tokyo"' is default value. 91 | var UserFactory = factory.NewFactory( 92 | &User{}, 93 | ).SeqInt("ID", func(n int) (interface{}, error) { 94 | return n, nil 95 | }).Attr("Name", func(args factory.Args) (interface{}, error) { 96 | return randomdata.FullName(randomdata.RandomGender), nil 97 | }).Attr("Location", func(args factory.Args) (interface{}, error) { 98 | return randomdata.City(), nil 99 | }) 100 | 101 | func main() { 102 | for i := 0; i < 3; i++ { 103 | user := UserFactory.MustCreate().(*User) 104 | fmt.Println("ID:", user.ID, " Name:", user.Name, " Location:", user.Location) 105 | } 106 | } 107 | ``` 108 | 109 | Output: 110 | 111 | ``` 112 | ID: 1 Name: Benjamin Thomas Location: Burrton 113 | ID: 2 Name: Madison Davis Location: Brandwell 114 | ID: 3 Name: Aubrey Robinson Location: Campden 115 | ``` 116 | 117 | ### Define a factory includes sub-factory 118 | 119 | ```go 120 | package main 121 | 122 | import ( 123 | "fmt" 124 | "github.com/bluele/factory-go/factory" 125 | ) 126 | 127 | type Group struct { 128 | ID int 129 | Name string 130 | } 131 | 132 | type User struct { 133 | ID int 134 | Name string 135 | Location string 136 | Group *Group 137 | } 138 | 139 | var GroupFactory = factory.NewFactory( 140 | &Group{}, 141 | ).SeqInt("ID", func(n int) (interface{}, error) { 142 | return 2 - n%2, nil 143 | }).Attr("Name", func(args factory.Args) (interface{}, error) { 144 | group := args.Instance().(*Group) 145 | return fmt.Sprintf("group-%d", group.ID), nil 146 | }) 147 | 148 | // 'Location: "Tokyo"' is default value. 149 | var UserFactory = factory.NewFactory( 150 | &User{Location: "Tokyo"}, 151 | ).SeqInt("ID", func(n int) (interface{}, error) { 152 | return n, nil 153 | }).Attr("Name", func(args factory.Args) (interface{}, error) { 154 | user := args.Instance().(*User) 155 | return fmt.Sprintf("user-%d", user.ID), nil 156 | }).SubFactory("Group", GroupFactory) 157 | 158 | func main() { 159 | for i := 0; i < 3; i++ { 160 | user := UserFactory.MustCreate().(*User) 161 | fmt.Println( 162 | "ID:", user.ID, " Name:", user.Name, " Location:", user.Location, 163 | " Group.ID:", user.Group.ID, " Group.Name", user.Group.Name) 164 | } 165 | } 166 | ``` 167 | 168 | Output: 169 | 170 | ``` 171 | ID: 1 Name: user-1 Location: Tokyo Group.ID: 1 Group.Name group-1 172 | ID: 2 Name: user-2 Location: Tokyo Group.ID: 2 Group.Name group-2 173 | ID: 3 Name: user-3 Location: Tokyo Group.ID: 1 Group.Name group-1 174 | ``` 175 | 176 | ### Define a factory includes a slice for sub-factory. 177 | 178 | ```go 179 | package main 180 | 181 | import ( 182 | "fmt" 183 | "github.com/bluele/factory-go/factory" 184 | ) 185 | 186 | type Post struct { 187 | ID int 188 | Content string 189 | } 190 | 191 | type User struct { 192 | ID int 193 | Name string 194 | Posts []*Post 195 | } 196 | 197 | var PostFactory = factory.NewFactory( 198 | &Post{}, 199 | ).SeqInt("ID", func(n int) (interface{}, error) { 200 | return n, nil 201 | }).Attr("Content", func(args factory.Args) (interface{}, error) { 202 | post := args.Instance().(*Post) 203 | return fmt.Sprintf("post-%d", post.ID), nil 204 | }) 205 | 206 | var UserFactory = factory.NewFactory( 207 | &User{}, 208 | ).SeqInt("ID", func(n int) (interface{}, error) { 209 | return n, nil 210 | }).Attr("Name", func(args factory.Args) (interface{}, error) { 211 | user := args.Instance().(*User) 212 | return fmt.Sprintf("user-%d", user.ID), nil 213 | }).SubSliceFactory("Posts", PostFactory, func() int { return 3 }) 214 | 215 | func main() { 216 | for i := 0; i < 3; i++ { 217 | user := UserFactory.MustCreate().(*User) 218 | fmt.Println("ID:", user.ID, " Name:", user.Name) 219 | for _, post := range user.Posts { 220 | fmt.Printf("\tPost.ID: %v Post.Content: %v\n", post.ID, post.Content) 221 | } 222 | } 223 | } 224 | ``` 225 | 226 | Output: 227 | 228 | ``` 229 | ID: 1 Name: user-1 230 | Post.ID: 1 Post.Content: post-1 231 | Post.ID: 2 Post.Content: post-2 232 | Post.ID: 3 Post.Content: post-3 233 | ID: 2 Name: user-2 234 | Post.ID: 4 Post.Content: post-4 235 | Post.ID: 5 Post.Content: post-5 236 | Post.ID: 6 Post.Content: post-6 237 | ID: 3 Name: user-3 238 | Post.ID: 7 Post.Content: post-7 239 | Post.ID: 8 Post.Content: post-8 240 | Post.ID: 9 Post.Content: post-9 241 | ``` 242 | 243 | ### Define a factory includes sub-factory that contains self-reference. 244 | 245 | ```go 246 | package main 247 | 248 | import ( 249 | "fmt" 250 | "github.com/Pallinder/go-randomdata" 251 | "github.com/bluele/factory-go/factory" 252 | ) 253 | 254 | type User struct { 255 | ID int 256 | Name string 257 | CloseFriend *User 258 | } 259 | 260 | var UserFactory = factory.NewFactory( 261 | &User{}, 262 | ) 263 | 264 | func init() { 265 | UserFactory.SeqInt("ID", func(n int) (interface{}, error) { 266 | return n, nil 267 | }).Attr("Name", func(args factory.Args) (interface{}, error) { 268 | return randomdata.FullName(randomdata.RandomGender), nil 269 | }).SubRecursiveFactory("CloseFriend", UserFactory, func() int { return 2 }) // recursive depth is always 2 270 | } 271 | 272 | func main() { 273 | user := UserFactory.MustCreate().(*User) 274 | fmt.Println("ID:", user.ID, " Name:", user.Name, 275 | " CloseFriend.ID:", user.CloseFriend.ID, " CloseFriend.Name:", user.CloseFriend.Name) 276 | // `user.CloseFriend.CloseFriend.CloseFriend ` depth is 3, so this value is always nil. 277 | fmt.Printf("%v %v\n", user.CloseFriend.CloseFriend, user.CloseFriend.CloseFriend.CloseFriend) 278 | } 279 | ``` 280 | 281 | Output: 282 | ``` 283 | ID: 1 Name: Mia Williams CloseFriend.ID: 2 CloseFriend.Name: Joseph Wilson 284 | &{3 Liam Wilson } 285 | ``` 286 | 287 | ### Define a sub-factory refers to parent factory 288 | 289 | ```go 290 | package main 291 | 292 | import ( 293 | "fmt" 294 | "github.com/bluele/factory-go/factory" 295 | ) 296 | 297 | type User struct { 298 | ID int 299 | Name string 300 | Group *Group 301 | } 302 | 303 | type Group struct { 304 | ID int 305 | Name string 306 | Users []*User 307 | } 308 | 309 | var UserFactory = factory.NewFactory( 310 | &User{}, 311 | ).SeqInt("ID", func(n int) (interface{}, error) { 312 | return n, nil 313 | }).Attr("Name", func(args factory.Args) (interface{}, error) { 314 | user := args.Instance().(*User) 315 | return fmt.Sprintf("user-%d", user.ID), nil 316 | }).Attr("Group", func(args factory.Args) (interface{}, error) { 317 | if parent := args.Parent(); parent != nil { 318 | // if args have parent, use it. 319 | return parent.Instance(), nil 320 | } 321 | return nil, nil 322 | }) 323 | 324 | var GroupFactory = factory.NewFactory( 325 | &Group{}, 326 | ).SeqInt("ID", func(n int) (interface{}, error) { 327 | return 2 - n%2, nil 328 | }).Attr("Name", func(args factory.Args) (interface{}, error) { 329 | group := args.Instance().(*Group) 330 | return fmt.Sprintf("group-%d", group.ID), nil 331 | }).SubSliceFactory("Users", UserFactory, func() int { return 3 }) 332 | 333 | func main() { 334 | group := GroupFactory.MustCreate().(*Group) 335 | fmt.Println("Group.ID:", group.ID) 336 | for _, user := range group.Users { 337 | fmt.Println("\tUser.ID:", user.ID, " User.Name:", user.Name, " User.Group.ID:", user.Group.ID) 338 | } 339 | } 340 | ``` 341 | 342 | Output: 343 | ``` 344 | Group.ID: 1 345 | User.ID: 1 User.Name: user-1 User.Group.ID: 1 346 | User.ID: 2 User.Name: user-2 User.Group.ID: 1 347 | User.ID: 3 User.Name: user-3 User.Group.ID: 1 348 | ``` 349 | 350 | ## Persistent models 351 | 352 | Currently this project has no support for directly integration with ORM like [gorm](https://github.com/jinzhu/gorm), so you need to do manually. 353 | 354 | Here is an example: https://github.com/bluele/factory-go/blob/master/examples/gorm_integration.go 355 | 356 | # Author 357 | 358 | **Jun Kimura** 359 | 360 | * 361 | * 362 | -------------------------------------------------------------------------------- /factory/factory.go: -------------------------------------------------------------------------------- 1 | package factory 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "reflect" 7 | "strconv" 8 | "sync/atomic" 9 | ) 10 | 11 | var ( 12 | TagName = "factory" 13 | emptyValue = reflect.Value{} 14 | ) 15 | 16 | type Factory struct { 17 | model interface{} 18 | numField int 19 | rt reflect.Type 20 | rv *reflect.Value 21 | attrGens []*attrGenerator 22 | nameIndexMap map[string]int // pair for attribute name and field index. 23 | isPtr bool 24 | onCreate func(Args) error 25 | } 26 | 27 | type Args interface { 28 | Instance() interface{} 29 | Parent() Args 30 | Context() context.Context 31 | pipeline(int) *pipeline 32 | } 33 | 34 | type argsStruct struct { 35 | ctx context.Context 36 | rv *reflect.Value 37 | pl *pipeline 38 | } 39 | 40 | // Instance returns a object to which the generator declared just before is applied 41 | func (args *argsStruct) Instance() interface{} { 42 | return args.rv.Interface() 43 | } 44 | 45 | // Parent returns a parent argument if current factory is a subfactory of parent 46 | func (args *argsStruct) Parent() Args { 47 | if args.pl == nil { 48 | return nil 49 | } 50 | return args.pl.parent 51 | } 52 | 53 | func (args *argsStruct) pipeline(num int) *pipeline { 54 | if args.pl == nil { 55 | return newPipeline(num) 56 | } 57 | return args.pl 58 | } 59 | 60 | func (args *argsStruct) Context() context.Context { 61 | return args.ctx 62 | } 63 | 64 | func (args *argsStruct) UpdateContext(ctx context.Context) { 65 | args.ctx = ctx 66 | } 67 | 68 | type Stacks []*int64 69 | 70 | func (st *Stacks) Size(idx int) int64 { 71 | return *(*st)[idx] 72 | } 73 | 74 | // Set method is not goroutine safe. 75 | func (st *Stacks) Set(idx, val int) { 76 | var ini int64 = 0 77 | (*st)[idx] = &ini 78 | atomic.StoreInt64((*st)[idx], int64(val)) 79 | } 80 | 81 | func (st *Stacks) Push(idx, delta int) { 82 | atomic.AddInt64((*st)[idx], int64(delta)) 83 | } 84 | 85 | func (st *Stacks) Pop(idx, delta int) { 86 | atomic.AddInt64((*st)[idx], -int64(delta)) 87 | } 88 | 89 | func (st *Stacks) Next(idx int) bool { 90 | st.Pop(idx, 1) 91 | return *(*st)[idx] >= 0 92 | } 93 | 94 | func (st *Stacks) Has(idx int) bool { 95 | return (*st)[idx] != nil 96 | } 97 | 98 | type pipeline struct { 99 | stacks Stacks 100 | parent Args 101 | } 102 | 103 | func newPipeline(size int) *pipeline { 104 | return &pipeline{stacks: make(Stacks, size)} 105 | } 106 | 107 | func (pl *pipeline) Next(args Args) *pipeline { 108 | npl := &pipeline{} 109 | npl.parent = args 110 | npl.stacks = make(Stacks, len(pl.stacks)) 111 | for i, sptr := range pl.stacks { 112 | if sptr != nil { 113 | stack := *sptr 114 | npl.stacks[i] = &stack 115 | } 116 | } 117 | return npl 118 | } 119 | 120 | // NewFactory returns a new factory for specified model class 121 | // Each generator is applied in the order in which they are declared 122 | func NewFactory(model interface{}) *Factory { 123 | fa := &Factory{} 124 | fa.model = model 125 | fa.nameIndexMap = make(map[string]int) 126 | 127 | fa.init() 128 | return fa 129 | } 130 | 131 | type attrGenerator struct { 132 | genFunc func(Args) (interface{}, error) 133 | key string 134 | value interface{} 135 | isNil bool 136 | } 137 | 138 | func (fa *Factory) init() { 139 | rt := reflect.TypeOf(fa.model) 140 | rv := reflect.ValueOf(fa.model) 141 | 142 | fa.isPtr = rt.Kind() == reflect.Ptr 143 | 144 | if fa.isPtr { 145 | rt = rt.Elem() 146 | rv = rv.Elem() 147 | } 148 | 149 | fa.numField = rv.NumField() 150 | 151 | for i := 0; i < fa.numField; i++ { 152 | tf := rt.Field(i) 153 | vf := rv.Field(i) 154 | ag := &attrGenerator{} 155 | 156 | if !vf.CanSet() || (tf.Type.Kind() == reflect.Ptr && vf.IsNil()) { 157 | ag.isNil = true 158 | } else { 159 | ag.value = vf.Interface() 160 | } 161 | 162 | attrName := getAttrName(tf, TagName) 163 | ag.key = attrName 164 | fa.nameIndexMap[attrName] = i 165 | fa.attrGens = append(fa.attrGens, ag) 166 | } 167 | 168 | fa.rt = rt 169 | fa.rv = &rv 170 | } 171 | 172 | func (fa *Factory) modelName() string { 173 | return fa.rt.Name() 174 | } 175 | 176 | func (fa *Factory) Attr(name string, gen func(Args) (interface{}, error)) *Factory { 177 | idx := fa.checkIdx(name) 178 | fa.attrGens[idx].genFunc = gen 179 | return fa 180 | } 181 | 182 | func (fa *Factory) SeqInt(name string, gen func(int) (interface{}, error)) *Factory { 183 | idx := fa.checkIdx(name) 184 | var seq int64 = 0 185 | fa.attrGens[idx].genFunc = func(args Args) (interface{}, error) { 186 | new := atomic.AddInt64(&seq, 1) 187 | return gen(int(new)) 188 | } 189 | return fa 190 | } 191 | 192 | func (fa *Factory) SeqInt64(name string, gen func(int64) (interface{}, error)) *Factory { 193 | idx := fa.checkIdx(name) 194 | var seq int64 = 0 195 | fa.attrGens[idx].genFunc = func(args Args) (interface{}, error) { 196 | new := atomic.AddInt64(&seq, 1) 197 | return gen(new) 198 | } 199 | return fa 200 | } 201 | 202 | func (fa *Factory) SeqString(name string, gen func(string) (interface{}, error)) *Factory { 203 | idx := fa.checkIdx(name) 204 | var seq int64 = 0 205 | fa.attrGens[idx].genFunc = func(args Args) (interface{}, error) { 206 | new := atomic.AddInt64(&seq, 1) 207 | return gen(strconv.FormatInt(new, 10)) 208 | } 209 | return fa 210 | } 211 | 212 | func (fa *Factory) SubFactory(name string, sub *Factory) *Factory { 213 | idx := fa.checkIdx(name) 214 | fa.attrGens[idx].genFunc = func(args Args) (interface{}, error) { 215 | pipeline := args.pipeline(fa.numField) 216 | ret, err := sub.create(args.Context(), nil, pipeline.Next(args)) 217 | if err != nil { 218 | return nil, err 219 | } 220 | return ret, nil 221 | } 222 | return fa 223 | } 224 | 225 | func (fa *Factory) SubSliceFactory(name string, sub *Factory, getSize func() int) *Factory { 226 | idx := fa.checkIdx(name) 227 | tp := fa.rt.Field(idx).Type 228 | fa.attrGens[idx].genFunc = func(args Args) (interface{}, error) { 229 | size := getSize() 230 | pipeline := args.pipeline(fa.numField) 231 | sv := reflect.MakeSlice(tp, size, size) 232 | for i := 0; i < size; i++ { 233 | ret, err := sub.create(args.Context(), nil, pipeline.Next(args)) 234 | if err != nil { 235 | return nil, err 236 | } 237 | sv.Index(i).Set(reflect.ValueOf(ret)) 238 | } 239 | return sv.Interface(), nil 240 | } 241 | return fa 242 | } 243 | 244 | func (fa *Factory) SubRecursiveFactory(name string, sub *Factory, getLimit func() int) *Factory { 245 | idx := fa.checkIdx(name) 246 | fa.attrGens[idx].genFunc = func(args Args) (interface{}, error) { 247 | pl := args.pipeline(fa.numField) 248 | if !pl.stacks.Has(idx) { 249 | pl.stacks.Set(idx, getLimit()) 250 | } 251 | if pl.stacks.Next(idx) { 252 | ret, err := sub.create(args.Context(), nil, pl.Next(args)) 253 | if err != nil { 254 | return nil, err 255 | } 256 | return ret, nil 257 | } 258 | return nil, nil 259 | } 260 | return fa 261 | } 262 | 263 | func (fa *Factory) SubRecursiveSliceFactory(name string, sub *Factory, getSize, getLimit func() int) *Factory { 264 | idx := fa.checkIdx(name) 265 | tp := fa.rt.Field(idx).Type 266 | fa.attrGens[idx].genFunc = func(args Args) (interface{}, error) { 267 | pl := args.pipeline(fa.numField) 268 | if !pl.stacks.Has(idx) { 269 | pl.stacks.Set(idx, getLimit()) 270 | } 271 | if pl.stacks.Next(idx) { 272 | size := getSize() 273 | sv := reflect.MakeSlice(tp, size, size) 274 | for i := 0; i < size; i++ { 275 | ret, err := sub.create(args.Context(), nil, pl.Next(args)) 276 | if err != nil { 277 | return nil, err 278 | } 279 | sv.Index(i).Set(reflect.ValueOf(ret)) 280 | } 281 | return sv.Interface(), nil 282 | } 283 | return nil, nil 284 | } 285 | return fa 286 | } 287 | 288 | // OnCreate registers a callback on object creation. 289 | // If callback function returns error, object creation is failed. 290 | func (fa *Factory) OnCreate(cb func(Args) error) *Factory { 291 | fa.onCreate = cb 292 | return fa 293 | } 294 | 295 | func (fa *Factory) checkIdx(name string) int { 296 | idx, ok := fa.nameIndexMap[name] 297 | if !ok { 298 | panic("No such attribute name: " + name) 299 | } 300 | return idx 301 | } 302 | 303 | func (fa *Factory) Create() (interface{}, error) { 304 | return fa.CreateWithOption(nil) 305 | } 306 | 307 | func (fa *Factory) CreateWithOption(opt map[string]interface{}) (interface{}, error) { 308 | return fa.create(context.Background(), opt, nil) 309 | } 310 | 311 | func (fa *Factory) CreateWithContext(ctx context.Context) (interface{}, error) { 312 | return fa.create(ctx, nil, nil) 313 | } 314 | 315 | func (fa *Factory) CreateWithContextAndOption(ctx context.Context, opt map[string]interface{}) (interface{}, error) { 316 | return fa.create(ctx, opt, nil) 317 | } 318 | 319 | func (fa *Factory) MustCreate() interface{} { 320 | return fa.MustCreateWithOption(nil) 321 | } 322 | 323 | func (fa *Factory) MustCreateWithOption(opt map[string]interface{}) interface{} { 324 | return fa.MustCreateWithContextAndOption(context.Background(), opt) 325 | } 326 | 327 | func (fa *Factory) MustCreateWithContextAndOption(ctx context.Context, opt map[string]interface{}) interface{} { 328 | inst, err := fa.CreateWithContextAndOption(ctx, opt) 329 | if err != nil { 330 | panic(err) 331 | } 332 | return inst 333 | } 334 | 335 | /* 336 | Bind values of a new objects to a pointer to struct. 337 | 338 | ptr: a pointer to struct 339 | */ 340 | func (fa *Factory) Construct(ptr interface{}) error { 341 | return fa.ConstructWithOption(ptr, nil) 342 | } 343 | 344 | /* 345 | Bind values of a new objects to a pointer to struct with option. 346 | 347 | ptr: a pointer to struct 348 | opt: attibute values 349 | */ 350 | func (fa *Factory) ConstructWithOption(ptr interface{}, opt map[string]interface{}) error { 351 | return fa.ConstructWithContextAndOption(context.Background(), ptr, opt) 352 | } 353 | 354 | /* 355 | Bind values of a new objects to a pointer to struct with context and option. 356 | 357 | ctx: context object 358 | ptr: a pointer to struct 359 | opt: attibute values 360 | */ 361 | func (fa *Factory) ConstructWithContextAndOption(ctx context.Context, ptr interface{}, opt map[string]interface{}) error { 362 | pt := reflect.TypeOf(ptr) 363 | if pt.Kind() != reflect.Ptr { 364 | return errors.New("ptr should be pointer type.") 365 | } 366 | pt = pt.Elem() 367 | if pt.Name() != fa.modelName() { 368 | return errors.New("ptr type should be " + fa.modelName()) 369 | } 370 | 371 | inst := reflect.ValueOf(ptr).Elem() 372 | _, err := fa.build(ctx, &inst, pt, opt, nil) 373 | return err 374 | } 375 | 376 | func (fa *Factory) build(ctx context.Context, inst *reflect.Value, tp reflect.Type, opt map[string]interface{}, pl *pipeline) (interface{}, error) { 377 | args := &argsStruct{} 378 | args.pl = pl 379 | args.ctx = ctx 380 | if fa.isPtr { 381 | addr := (*inst).Addr() 382 | args.rv = &addr 383 | } else { 384 | args.rv = inst 385 | } 386 | 387 | for i := 0; i < fa.numField; i++ { 388 | if v, ok := opt[fa.attrGens[i].key]; ok { 389 | inst.Field(i).Set(reflect.ValueOf(v)) 390 | } else { 391 | ag := fa.attrGens[i] 392 | if ag.genFunc == nil { 393 | if !ag.isNil { 394 | inst.Field(i).Set(reflect.ValueOf(ag.value)) 395 | } 396 | } else { 397 | v, err := ag.genFunc(args) 398 | if err != nil { 399 | return nil, err 400 | } 401 | if v != nil { 402 | inst.Field(i).Set(reflect.ValueOf(v)) 403 | } 404 | } 405 | } 406 | } 407 | 408 | for k, v := range opt { 409 | setValueWithAttrPath(inst, tp, k, v) 410 | } 411 | 412 | if fa.onCreate != nil { 413 | if err := fa.onCreate(args); err != nil { 414 | return nil, err 415 | } 416 | } 417 | 418 | if fa.isPtr { 419 | return (*inst).Addr().Interface(), nil 420 | } 421 | return inst.Interface(), nil 422 | } 423 | 424 | func (fa *Factory) create(ctx context.Context, opt map[string]interface{}, pl *pipeline) (interface{}, error) { 425 | inst := reflect.New(fa.rt).Elem() 426 | return fa.build(ctx, &inst, fa.rt, opt, pl) 427 | } 428 | -------------------------------------------------------------------------------- /factory/factory_test.go: -------------------------------------------------------------------------------- 1 | package factory 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | "testing" 7 | ) 8 | 9 | func TestFactory(t *testing.T) { 10 | type User struct { 11 | ID int 12 | Name string 13 | Location string 14 | unexported string 15 | } 16 | 17 | userFactory := NewFactory(&User{Location: "Tokyo"}). 18 | SeqInt("ID", func(n int) (interface{}, error) { 19 | return n, nil 20 | }). 21 | Attr("Name", func(args Args) (interface{}, error) { 22 | return "bluele", nil 23 | }) 24 | 25 | iuser, err := userFactory.Create() 26 | if err != nil { 27 | t.Error(err) 28 | } 29 | user, ok := iuser.(*User) 30 | if !ok { 31 | t.Error("It should be *User type.") 32 | return 33 | } 34 | if user.ID != 1 { 35 | t.Error("user.ID should be 1.") 36 | return 37 | } 38 | if user.Name != "bluele" { 39 | t.Error(`user.Name should be "bluele".`) 40 | return 41 | } 42 | if user.Location != "Tokyo" { 43 | t.Error(`user.Location should be "Tokyo".`) 44 | return 45 | } 46 | } 47 | 48 | func TestMapAttrFactory(t *testing.T) { 49 | type User struct { 50 | ID int 51 | Ext map[string]string 52 | } 53 | var userFactory = NewFactory(&User{}). 54 | SeqInt("ID", func(n int) (interface{}, error) { 55 | return n, nil 56 | }). 57 | Attr("Ext", func(args Args) (interface{}, error) { 58 | return map[string]string{"test": "ok"}, nil 59 | }) 60 | user := &User{} 61 | if err := userFactory.Construct(user); err != nil { 62 | t.Error(err) 63 | return 64 | } 65 | if user.ID == 0 { 66 | t.Error("user.ID should not be 0.") 67 | } 68 | if v, ok := user.Ext["test"]; !ok { 69 | t.Error("user.Ext[\"test\"] should not be empty.") 70 | } else if v != "ok" { 71 | t.Error("user.Ext[\"test\"] should be ok.") 72 | } 73 | } 74 | 75 | func TestSubFactory(t *testing.T) { 76 | type Group struct { 77 | ID int 78 | } 79 | type User struct { 80 | ID int 81 | Name string 82 | Group *Group 83 | } 84 | 85 | groupFactory := NewFactory(&Group{}). 86 | SeqInt("ID", func(n int) (interface{}, error) { 87 | return n, nil 88 | }) 89 | 90 | userFactory := NewFactory(&User{}). 91 | SeqInt("ID", func(n int) (interface{}, error) { 92 | return n, nil 93 | }). 94 | Attr("Name", func(args Args) (interface{}, error) { 95 | return "bluele", nil 96 | }). 97 | SubFactory("Group", groupFactory) 98 | 99 | iuser, err := userFactory.Create() 100 | if err != nil { 101 | t.Error(err) 102 | } 103 | user, ok := iuser.(*User) 104 | if !ok { 105 | t.Error("It should be *User type.") 106 | return 107 | } 108 | 109 | if user.Group == nil { 110 | t.Error("user.Group should be *Group type.") 111 | return 112 | } 113 | 114 | if user.Group.ID != 1 { 115 | t.Error("user.Group.ID should be 1.") 116 | return 117 | } 118 | } 119 | 120 | func TestSubSliceFactory(t *testing.T) { 121 | type Group struct { 122 | ID int 123 | } 124 | type User struct { 125 | ID int 126 | Name string 127 | Groups []*Group 128 | } 129 | 130 | groupFactory := NewFactory(&Group{}). 131 | SeqInt("ID", func(n int) (interface{}, error) { 132 | return n, nil 133 | }) 134 | 135 | userFactory := NewFactory(&User{}). 136 | SeqInt("ID", func(n int) (interface{}, error) { 137 | return n, nil 138 | }). 139 | Attr("Name", func(args Args) (interface{}, error) { 140 | return "bluele", nil 141 | }). 142 | SubSliceFactory("Groups", groupFactory, func() int { return 3 }) 143 | 144 | iuser, err := userFactory.Create() 145 | if err != nil { 146 | t.Error(err) 147 | } 148 | user, ok := iuser.(*User) 149 | if !ok { 150 | t.Error("It should be *User type.") 151 | return 152 | } 153 | 154 | if user.Groups == nil { 155 | t.Error("user.Groups should be []*Group type.") 156 | return 157 | } 158 | 159 | if len(user.Groups) != 3 { 160 | t.Error("len(user.Groups) should be 3.") 161 | return 162 | } 163 | 164 | for i := 0; i < 3; i++ { 165 | if user.Groups[i].ID != i+1 { 166 | t.Errorf("user.Groups[%v].ID should be %v", i, i+1) 167 | return 168 | } 169 | } 170 | } 171 | 172 | func TestSubRecursiveFactory(t *testing.T) { 173 | type User struct { 174 | ID int 175 | Name string 176 | Friend *User 177 | } 178 | 179 | var userFactory = NewFactory(&User{}) 180 | userFactory. 181 | SeqInt("ID", func(n int) (interface{}, error) { 182 | return n, nil 183 | }). 184 | Attr("Name", func(args Args) (interface{}, error) { 185 | return "bluele", nil 186 | }). 187 | SubRecursiveFactory("Friend", userFactory, func() int { return 2 }) 188 | 189 | iuser, err := userFactory.Create() 190 | if err != nil { 191 | t.Error(err) 192 | return 193 | } 194 | user, ok := iuser.(*User) 195 | if !ok { 196 | t.Error("It should be *User type.") 197 | return 198 | } 199 | 200 | if user.Friend.Friend == nil { 201 | t.Error("user.Friend.Friend should not be nil.") 202 | return 203 | } 204 | 205 | if user.Friend.Friend.Friend != nil { 206 | t.Error("user.Friend.Friend.Friend should be nil.") 207 | return 208 | } 209 | } 210 | 211 | func TestFactoryConstruction(t *testing.T) { 212 | type User struct { 213 | ID int 214 | Name string 215 | } 216 | 217 | var userFactory = NewFactory(&User{}). 218 | SeqInt("ID", func(n int) (interface{}, error) { 219 | return n, nil 220 | }). 221 | Attr("Name", func(args Args) (interface{}, error) { 222 | return "bluele", nil 223 | }) 224 | 225 | var user *User 226 | 227 | user = &User{} 228 | if err := userFactory.Construct(user); err != nil { 229 | t.Error(err) 230 | return 231 | } 232 | if user.ID == 0 { 233 | t.Error("user.ID should not be 0.") 234 | } 235 | if user.Name == "" { 236 | t.Error("user.ID should not be empty.") 237 | } 238 | 239 | user = &User{} 240 | if err := userFactory.ConstructWithOption(user, map[string]interface{}{"Name": "jun"}); err != nil { 241 | t.Error(err) 242 | return 243 | } 244 | if user.ID == 0 { 245 | t.Error("user.ID should not be 0.") 246 | } 247 | if user.Name == "" { 248 | t.Error("user.ID should not be empty.") 249 | } 250 | } 251 | 252 | func TestFactoryWhenCallArgsParent(t *testing.T) { 253 | type User struct { 254 | Name string 255 | GroupUUID string 256 | } 257 | 258 | var userFactory = NewFactory(&User{}) 259 | userFactory. 260 | Attr("Name", func(args Args) (interface{}, error) { 261 | if parent := args.Parent(); parent != nil { 262 | if pUser, ok := parent.Instance().(*User); ok { 263 | return pUser.GroupUUID, nil 264 | } 265 | } 266 | return "", nil 267 | }) 268 | 269 | if err := userFactory.Construct(&User{}); err != nil { 270 | t.Error(err) 271 | return 272 | } 273 | } 274 | 275 | func TestFactoryWithOptions(t *testing.T) { 276 | type ( 277 | Group struct { 278 | Name string 279 | } 280 | User struct { 281 | ID int 282 | Name string 283 | Group1 Group 284 | Group2 *Group 285 | } 286 | ) 287 | 288 | var userFactory = NewFactory(&User{}) 289 | user := userFactory.MustCreateWithOption(map[string]interface{}{ 290 | "ID": 1, 291 | "Name": "bluele", 292 | "Group1.Name": "programmer", 293 | "Group2.Name": "web", 294 | }).(*User) 295 | 296 | if user.ID != 1 { 297 | t.Errorf("user.ID should be 1, not %v", user.ID) 298 | } 299 | 300 | if user.Name != "bluele" { 301 | t.Errorf("user.Name should be bluele, not %v", user.Name) 302 | } 303 | 304 | if user.Group1.Name != "programmer" { 305 | t.Errorf("user.Group1.Name should be programmer, not %v", user.Group1.Name) 306 | } 307 | 308 | if user.Group2.Name != "web" { 309 | t.Errorf("user.Group2.Name should be web, not %v", user.Group2.Name) 310 | } 311 | } 312 | 313 | func TestFactoryMuctCreateWithContextAndOptions(t *testing.T) { 314 | type User struct { 315 | ID int 316 | Name string 317 | } 318 | 319 | type ctxField int 320 | const nameField ctxField = 1 321 | 322 | var userFactory = NewFactory(&User{}) 323 | 324 | t.Run("with valid options", func(t *testing.T) { 325 | user := userFactory.MustCreateWithContextAndOption(context.Background(), map[string]interface{}{ 326 | "ID": 1, 327 | "Name": "bluele", 328 | }).(*User) 329 | 330 | if user.ID != 1 { 331 | t.Errorf("user.ID should be 1, not %v", user.ID) 332 | } 333 | 334 | if user.Name != "bluele" { 335 | t.Errorf("user.Name should be bluele, not %v", user.Name) 336 | } 337 | }) 338 | 339 | t.Run("with broken options", func(t *testing.T) { 340 | defer func() { 341 | if recover() == nil { 342 | t.Errorf("func should panic") 343 | } 344 | }() 345 | 346 | userFactory.MustCreateWithContextAndOption(context.Background(), map[string]interface{}{ 347 | "ID": 1, 348 | "Name": 3, 349 | }) 350 | }) 351 | 352 | t.Run("with filled context", func(t *testing.T) { 353 | userFactory := NewFactory(&User{}).Attr("Name", func(args Args) (interface{}, error) { 354 | return args.Context().Value(nameField), nil 355 | }) 356 | 357 | ctx := context.WithValue(context.Background(), nameField, "bluele from ctx") 358 | user := userFactory.MustCreateWithContextAndOption(ctx, map[string]interface{}{ 359 | "ID": 1, 360 | }).(*User) 361 | 362 | if user.Name != "bluele from ctx" { 363 | t.Errorf("user.Name should be bluele from ctx, not %v", user.Name) 364 | } 365 | }) 366 | 367 | t.Run("with nil context", func(t *testing.T) { 368 | userFactory := NewFactory(&User{}).Attr("Name", func(args Args) (interface{}, error) { 369 | return args.Context().Value(nameField), nil 370 | }) 371 | 372 | defer func() { 373 | if recover() == nil { 374 | t.Errorf("func should panic") 375 | } 376 | }() 377 | 378 | userFactory.MustCreateWithContextAndOption(nil, map[string]interface{}{ 379 | "ID": 1, 380 | }) 381 | }) 382 | } 383 | 384 | func TestFactorySeqConcurrency(t *testing.T) { 385 | type User struct { 386 | ID int 387 | Name string 388 | } 389 | 390 | var userFactory = NewFactory(&User{}). 391 | SeqInt("ID", func(n int) (interface{}, error) { 392 | return n, nil 393 | }). 394 | SeqString("Name", func(s string) (interface{}, error) { 395 | return "user-" + s, nil 396 | }) 397 | 398 | var wg sync.WaitGroup 399 | users := make([]*User, 1000) 400 | 401 | // Concurrently construct many different Users 402 | for i := range users { 403 | i := i 404 | wg.Add(1) 405 | go func() { 406 | defer wg.Done() 407 | user, err := userFactory.Create() 408 | if err != nil { 409 | t.Errorf("constructing a User shouldn't have failed: %v", err) 410 | } else { 411 | users[i] = user.(*User) 412 | } 413 | }() 414 | } 415 | wg.Wait() 416 | 417 | // Check that each ID and Name value is unique 418 | ids := make(map[int]bool) 419 | names := make(map[string]bool) 420 | 421 | for _, user := range users { 422 | if ids[user.ID] { 423 | t.Errorf("found a repeated integer sequence value %d (user.ID)", user.ID) 424 | } else { 425 | ids[user.ID] = true 426 | } 427 | 428 | if names[user.Name] { 429 | t.Errorf("found a repeated string sequence value %s (user.Name)", user.Name) 430 | } else { 431 | names[user.Name] = true 432 | } 433 | } 434 | } 435 | 436 | func TestFactorySeqIntStartsAt1(t *testing.T) { 437 | type User struct { 438 | ID int 439 | } 440 | 441 | var userFactory = NewFactory(&User{}). 442 | SeqInt("ID", func(n int) (interface{}, error) { 443 | return n, nil 444 | }) 445 | 446 | user, err := userFactory.Create() 447 | if err != nil { 448 | t.Errorf("failed to create a User: %v", err) 449 | } 450 | 451 | if id := user.(*User).ID; id != 1 { 452 | t.Errorf("the starting number for SeqInt was %d, not 1", id) 453 | } 454 | } 455 | 456 | func TestFactorySeqInt64StartsAt1(t *testing.T) { 457 | type User struct { 458 | ID int64 459 | } 460 | 461 | var userFactory = NewFactory(&User{}). 462 | SeqInt64("ID", func(n int64) (interface{}, error) { 463 | return n, nil 464 | }) 465 | 466 | user, err := userFactory.Create() 467 | if err != nil { 468 | t.Errorf("failed to create a User: %v", err) 469 | } 470 | 471 | if id := user.(*User).ID; id != 1 { 472 | t.Errorf("the starting number for SeqInt was %d, not 1", id) 473 | } 474 | } 475 | 476 | func TestFactorySeqStringStartsAt1(t *testing.T) { 477 | type User struct { 478 | Name string 479 | } 480 | 481 | var userFactory = NewFactory(&User{}). 482 | SeqString("Name", func(s string) (interface{}, error) { 483 | return s, nil 484 | }) 485 | 486 | user, err := userFactory.Create() 487 | if err != nil { 488 | t.Errorf("failed to create a User: %v", err) 489 | } 490 | 491 | if name := user.(*User).Name; name != "1" { 492 | t.Errorf("the starting number for SeqString was %s, not 1", name) 493 | } 494 | } 495 | --------------------------------------------------------------------------------