factory-go is a fixtures replacement inspired by factory_boy and factory_bot.
It can be generated easily complex objects by using this, and maintain easily those objects generaters.
$ go get -u github.com/bluele/factory-go/factory
All of the following code on examples.
- Define a simple factory
- Use factory with random yet realistic values
- Define a factory includes sub-factory
- Define a factory includes a slice for sub-factory
- Define a factory includes sub-factory that contains self-reference
- Define a sub-factory refers to parent factory
Declare an factory has a set of simple attribute, and generate a fixture object.
package main
import (
"fmt"
"github.com/bluele/factory-go/factory"
)
type User struct {
ID int
Name string
Location string
}
// 'Location: "Tokyo"' is default value.
var UserFactory = factory.NewFactory(
&User{Location: "Tokyo"},
).SeqInt("ID", func(n int) (interface{}, error) {
return n, nil
}).Attr("Name", func(args factory.Args) (interface{}, error) {
user := args.Instance().(*User)
return fmt.Sprintf("user-%d", user.ID), nil
})
func main() {
for i := 0; i < 3; i++ {
user := UserFactory.MustCreate().(*User)
fmt.Println("ID:", user.ID, " Name:", user.Name, " Location:", user.Location)
}
}
Output:
ID: 1 Name: user-1 Location: Tokyo
ID: 2 Name: user-2 Location: Tokyo
ID: 3 Name: user-3 Location: Tokyo
Tests look better with random yet realistic values. For example, you can use go-randomdata library to get them:
package main
import (
"fmt"
"github.com/Pallinder/go-randomdata"
"github.com/bluele/factory-go/factory"
)
type User struct {
ID int
Name string
Location string
}
// 'Location: "Tokyo"' is default value.
var UserFactory = factory.NewFactory(
&User{},
).SeqInt("ID", func(n int) (interface{}, error) {
return n, nil
}).Attr("Name", func(args factory.Args) (interface{}, error) {
return randomdata.FullName(randomdata.RandomGender), nil
}).Attr("Location", func(args factory.Args) (interface{}, error) {
return randomdata.City(), nil
})
func main() {
for i := 0; i < 3; i++ {
user := UserFactory.MustCreate().(*User)
fmt.Println("ID:", user.ID, " Name:", user.Name, " Location:", user.Location)
}
}
Output:
ID: 1 Name: Benjamin Thomas Location: Burrton
ID: 2 Name: Madison Davis Location: Brandwell
ID: 3 Name: Aubrey Robinson Location: Campden
package main
import (
"fmt"
"github.com/bluele/factory-go/factory"
)
type Group struct {
ID int
Name string
}
type User struct {
ID int
Name string
Location string
Group *Group
}
var GroupFactory = factory.NewFactory(
&Group{},
).SeqInt("ID", func(n int) (interface{}, error) {
return 2 - n%2, nil
}).Attr("Name", func(args factory.Args) (interface{}, error) {
group := args.Instance().(*Group)
return fmt.Sprintf("group-%d", group.ID), nil
})
// 'Location: "Tokyo"' is default value.
var UserFactory = factory.NewFactory(
&User{Location: "Tokyo"},
).SeqInt("ID", func(n int) (interface{}, error) {
return n, nil
}).Attr("Name", func(args factory.Args) (interface{}, error) {
user := args.Instance().(*User)
return fmt.Sprintf("user-%d", user.ID), nil
}).SubFactory("Group", GroupFactory)
func main() {
for i := 0; i < 3; i++ {
user := UserFactory.MustCreate().(*User)
fmt.Println(
"ID:", user.ID, " Name:", user.Name, " Location:", user.Location,
" Group.ID:", user.Group.ID, " Group.Name", user.Group.Name)
}
}
Output:
ID: 1 Name: user-1 Location: Tokyo Group.ID: 1 Group.Name group-1
ID: 2 Name: user-2 Location: Tokyo Group.ID: 2 Group.Name group-2
ID: 3 Name: user-3 Location: Tokyo Group.ID: 1 Group.Name group-1
package main
import (
"fmt"
"github.com/bluele/factory-go/factory"
)
type Post struct {
ID int
Content string
}
type User struct {
ID int
Name string
Posts []*Post
}
var PostFactory = factory.NewFactory(
&Post{},
).SeqInt("ID", func(n int) (interface{}, error) {
return n, nil
}).Attr("Content", func(args factory.Args) (interface{}, error) {
post := args.Instance().(*Post)
return fmt.Sprintf("post-%d", post.ID), nil
})
var UserFactory = factory.NewFactory(
&User{},
).SeqInt("ID", func(n int) (interface{}, error) {
return n, nil
}).Attr("Name", func(args factory.Args) (interface{}, error) {
user := args.Instance().(*User)
return fmt.Sprintf("user-%d", user.ID), nil
}).SubSliceFactory("Posts", PostFactory, func() int { return 3 })
func main() {
for i := 0; i < 3; i++ {
user := UserFactory.MustCreate().(*User)
fmt.Println("ID:", user.ID, " Name:", user.Name)
for _, post := range user.Posts {
fmt.Printf("\tPost.ID: %v Post.Content: %v\n", post.ID, post.Content)
}
}
}
Output:
ID: 1 Name: user-1
Post.ID: 1 Post.Content: post-1
Post.ID: 2 Post.Content: post-2
Post.ID: 3 Post.Content: post-3
ID: 2 Name: user-2
Post.ID: 4 Post.Content: post-4
Post.ID: 5 Post.Content: post-5
Post.ID: 6 Post.Content: post-6
ID: 3 Name: user-3
Post.ID: 7 Post.Content: post-7
Post.ID: 8 Post.Content: post-8
Post.ID: 9 Post.Content: post-9
package main
import (
"fmt"
"github.com/Pallinder/go-randomdata"
"github.com/bluele/factory-go/factory"
)
type User struct {
ID int
Name string
CloseFriend *User
}
var UserFactory = factory.NewFactory(
&User{},
)
func init() {
UserFactory.SeqInt("ID", func(n int) (interface{}, error) {
return n, nil
}).Attr("Name", func(args factory.Args) (interface{}, error) {
return randomdata.FullName(randomdata.RandomGender), nil
}).SubRecursiveFactory("CloseFriend", UserFactory, func() int { return 2 }) // recursive depth is always 2
}
func main() {
user := UserFactory.MustCreate().(*User)
fmt.Println("ID:", user.ID, " Name:", user.Name,
" CloseFriend.ID:", user.CloseFriend.ID, " CloseFriend.Name:", user.CloseFriend.Name)
// `user.CloseFriend.CloseFriend.CloseFriend ` depth is 3, so this value is always nil.
fmt.Printf("%v %v\n", user.CloseFriend.CloseFriend, user.CloseFriend.CloseFriend.CloseFriend)
}
Output:
ID: 1 Name: Mia Williams CloseFriend.ID: 2 CloseFriend.Name: Joseph Wilson
&{3 Liam Wilson <nil>} <nil>
package main
import (
"fmt"
"github.com/bluele/factory-go/factory"
)
type User struct {
ID int
Name string
Group *Group
}
type Group struct {
ID int
Name string
Users []*User
}
var UserFactory = factory.NewFactory(
&User{},
).SeqInt("ID", func(n int) (interface{}, error) {
return n, nil
}).Attr("Name", func(args factory.Args) (interface{}, error) {
user := args.Instance().(*User)
return fmt.Sprintf("user-%d", user.ID), nil
}).Attr("Group", func(args factory.Args) (interface{}, error) {
if parent := args.Parent(); parent != nil {
// if args have parent, use it.
return parent.Instance(), nil
}
return nil, nil
})
var GroupFactory = factory.NewFactory(
&Group{},
).SeqInt("ID", func(n int) (interface{}, error) {
return 2 - n%2, nil
}).Attr("Name", func(args factory.Args) (interface{}, error) {
group := args.Instance().(*Group)
return fmt.Sprintf("group-%d", group.ID), nil
}).SubSliceFactory("Users", UserFactory, func() int { return 3 })
func main() {
group := GroupFactory.MustCreate().(*Group)
fmt.Println("Group.ID:", group.ID)
for _, user := range group.Users {
fmt.Println("\tUser.ID:", user.ID, " User.Name:", user.Name, " User.Group.ID:", user.Group.ID)
}
}
Output:
Group.ID: 1
User.ID: 1 User.Name: user-1 User.Group.ID: 1
User.ID: 2 User.Name: user-2 User.Group.ID: 1
User.ID: 3 User.Name: user-3 User.Group.ID: 1
Currently this project has no support for directly integration with ORM like gorm, so you need to do manually.
Here is an example: https://github.com/bluele/factory-go/blob/master/examples/gorm_integration.go
Jun Kimura