Skip to content

A library for setting up Golang objects inspired by factory_bot.

License

Notifications You must be signed in to change notification settings

bluele/factory-go

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

38 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

factory-go

Test GoDoc

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.

Install

$ go get -u github.com/bluele/factory-go/factory

Usage

All of the following code on examples.

Define a simple 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

Use factory with random yet realistic values.

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

Define a factory includes sub-factory

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

Define a factory includes a slice for sub-factory.

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

Define a factory includes sub-factory that contains self-reference.

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>

Define a sub-factory refers to parent factory

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

Persistent models

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

Author

Jun Kimura

About

A library for setting up Golang objects inspired by factory_bot.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages