Fixtory is a test fixture factory generator which generates type-safe, DRY, flexible fixture factory.
By using Fixtory...
- No more redundant repeated field value setting
- No more type assertion to convert from interface
- No more error handling when building fixture
- No more exhaustion to just prepare test data
$ go get github.com/k-yomo/fixtory/cmd/fixtory
Usage of fixtory:
fixtory [flags] -type T [directory]
Flags:
-output string
output file name; default srcdir/fixtory_gen.go
-package string
package name; default same package as the type
-type string
comma-separated list of type names; must be set
Complete code is in example.
- Add
go:generate
comment to generate factories
//go:generate fixtory -type=Author,Article -output=article.fixtory.go
// Author represents article's author
type Author struct {
ID int
Name string
}
// Article represents article
type Article struct {
ID int
Title string
Body string
AuthorID int
PublishScheduledAt time.Time
PublishedAt time.Time
Status ArticleStatus
LikeCount int
}
- Generate fixture factories
$ go generate ./...
- Use factory to initialize fixtures
var authorBluePrint = func(i int, last Author) Author {
num := i + 1
return Author{
ID: num,
Name: fmt.Sprintf("Author %d", num),
}
}
var articleBluePrint = func(i int, last Article) Article {
num := i + 1
return Article{
ID: num,
Title: fmt.Sprintf("Article %d", i+1),
AuthorID: num,
PublishScheduledAt: time.Now().Add(-1 * time.Hour),
PublishedAt: time.Now().Add(-1 * time.Hour),
LikeCount: 15,
}
}
func TestArticleList_SelectAuthoredBy(t *testing.T) {
authorFactory := NewAuthorFactory(t)
articleFactory := NewArticleFactory(t)
author1, author2 := authorFactory.NewBuilder(authorBluePrint).Build2()
articlesAuthoredBy1 := articleFactory.NewBuilder(articleBluePrint, Article{AuthorID: author1.ID}).BuildList(4)
articleAuthoredBy2 := articleFactory.NewBuilder(articleBluePrint, Article{AuthorID: author2.ID}).Build()
type args struct {
authorID int
}
tests := []struct {
name string
list ArticleList
args args
want ArticleList
}{
{
name: "returns articles authored by author 1",
list: append(articlesAuthoredBy1, articleAuthoredBy2),
args: args{authorID: author1.ID},
want: articlesAuthoredBy1,
},
{
name: "returns articles authored by author 2",
list: append(articlesAuthoredBy1, articleAuthoredBy2),
args: args{authorID: author2.ID},
want: ArticleList{articleAuthoredBy2},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := tt.list.SelectAuthoredBy(tt.args.authorID); !reflect.DeepEqual(got, tt.want) {
t.Errorf("SelectAuthoredBy() = %v, want %v", got, tt.want)
}
})
}
}
There are 4 layers in the process of initializing fixture in fixtory. The layers are stacked and each one is a delta of the changes from the previous layer like Dockerfile.
Blueprint is the base of fixture(like FROM in Dockerfile) and called first. You need to implement blueprint function to meet generated blueprint type (like below) It should return instance with generic field values.
type TestArticleBluePrintFunc func(i int, last Article) Article
To overwrite some fields, you can use traits. Traits are applied in the order of arguments to all fixtures. ※ Only non-zero value will be set.
// Repeatedly used trait would be better to define as global variable.
var articleTraitPublished = Article{
Status: ArticleStatusOpen,
PublishScheduledAt: time.Now().Add(-1 * time.Hour),
PublishedAt: time.Now().Add(-1 * time.Hour),
LikeCount: 15,
}
// recently published articles
articles:= articleFactory.NewBuilder(
nil,
articleTraitPublished,
Article{AuthorID: 5, PublishedAt: time.Now().Add(-1 * time.Minute)},
).BuildList(2)
When you want to overwrite a specific fixture in the list, use EachParam. Each Param overwrites the same index struct as parameter. ※ Only non-zero value will be set.
articleFactory := NewArticleFactory(t)
articles := articleFactory.NewBuilder(nil, Article{Title: "test article"})
.EachParam(Article{AuthorID: 1}, Article{AuthorID: 2}, Article{AuthorID: 2})
.BuildList(3)
Since there is no way to distinguish default zero value or intentionally set zero in params, you can overwrite fields with zero value like below, and it will be applied at the last minute.
articleFactory := NewArticleFactory(t)
// AuthorID will be overwritten with zero value.
articles := articleFactory.NewBuilder(articleBluePrint, Article{AuthorID: author1.ID}).
Zero(ArticleAuthorIDField).
BuildList(4)