factory-go

A test fixtures replacement inspired by factory_boy and factory_girl.

Github stars Tracking Chart

factory-go

wercker statusGoDoc

factory-go is a is a fixtures replacement inspired by factory_boy and factory_girl.

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

Main metrics

Overview
Name With Ownerbluele/factory-go
Primary LanguageGo
Program languageGo (Language Count: 1)
Platform
License:MIT License
所有者活动
Created At2015-10-04 12:56:57
Pushed At2023-04-05 10:47:32
Last Commit At2021-02-21 23:04:57
Release Count1
Last Release Namev0.0.1 (Posted on )
First Release Namev0.0.1 (Posted on )
用户参与
Stargazers Count382
Watchers Count9
Fork Count22
Commits Count38
Has Issues Enabled
Issues Count13
Issue Open Count4
Pull Requests Count15
Pull Requests Open Count1
Pull Requests Close Count0
项目设置
Has Wiki Enabled
Is Archived
Is Fork
Is Locked
Is Mirror
Is Private