ffakes is a tool to generate fakes for interfaces in Go code. It generates a new file with the same name as the original file with "_fakes" appended to the name. The generated file contains a struct with fields for each method in the interface where each field is a slice of functions that can be used to fake the series of calls to the function. The generated file also contains a type alias for each function type and a set of options to configure the fake. The fake struct can also be configured with the same options after initialization. The generated file is placed in the same directory as the original file by default but can be overridden with the output flag. If there is more calls to a method than the number of functions in the slice, while being used for testing the testing.T.Fatal function is called for the *testing.T object required for the New method.
go install github.com/zarldev/ffakes@latest
_____ _____ _____ __ ________ _____
/ __/ __/ _ | | / __/ ___>
| __| __| _ | _ <| __|___ |
\__/ \__/ \__|__|__|__\_____<_____/
Usage: ffakes [options] filename
Options:
-h
-help
Print help information
-i string
-interfaces string
Comma separated list of interfaces to fake
-o string
-output string
Output directory for the generated files
-v
-version
Print version information
-vv
-vverbose
Print verbose output
Define the interface to be faked and the corresponding go:generate
command
package repository
type User struct {
ID int
Name string
}
//go:generate ffakes -i UserRepository
type UserRepository interface {
FindUserByID(id int) (u User, err error)
CreateUser(user User) error
DeleteUserByID(id int) error
UpdateUser(old, new User) error
Execute(user User) error
FindUserByName(name string) (User, error)
}
Running go generate
now produces the following file:
// Code generated by ffakes v0.0.1 DO NOT EDIT.
package repository
import (
"testing"
)
type FakeUserRepository struct {
t *testing.T
FindUserByIDCount int
CreateUserCount int
DeleteUserByIDCount int
UpdateUserCount int
ExecuteCount int
FindUserByNameCount int
FFindUserByID []func(id int) (u User, err error)
FCreateUser []func(user User) error
FDeleteUserByID []func(id int) error
FUpdateUser []func(old, new User) error
FExecute []func(user User) error
FFindUserByName []func(name string) (User, error)
}
type FindUserByIDFunc = func(id int) (u User, err error)
type CreateUserFunc = func(user User) error
type DeleteUserByIDFunc = func(id int) error
type UpdateUserFunc = func(old, new User) error
type ExecuteFunc = func(user User) error
type FindUserByNameFunc = func(name string) (User, error)
type UserRepositoryOption func(f *FakeUserRepository)
func OnFindUserByID(fn ...FindUserByIDFunc) UserRepositoryOption {
return func(f *FakeUserRepository) {
f.FFindUserByID = append(f.FFindUserByID, fn...)
}
}
func OnCreateUser(fn ...CreateUserFunc) UserRepositoryOption {
return func(f *FakeUserRepository) {
f.FCreateUser = append(f.FCreateUser, fn...)
}
}
func OnDeleteUserByID(fn ...DeleteUserByIDFunc) UserRepositoryOption {
return func(f *FakeUserRepository) {
f.FDeleteUserByID = append(f.FDeleteUserByID, fn...)
}
}
func OnUpdateUser(fn ...UpdateUserFunc) UserRepositoryOption {
return func(f *FakeUserRepository) {
f.FUpdateUser = append(f.FUpdateUser, fn...)
}
}
func OnExecute(fn ...ExecuteFunc) UserRepositoryOption {
return func(f *FakeUserRepository) {
f.FExecute = append(f.FExecute, fn...)
}
}
func OnFindUserByName(fn ...FindUserByNameFunc) UserRepositoryOption {
return func(f *FakeUserRepository) {
f.FFindUserByName = append(f.FFindUserByName, fn...)
}
}
func (f *FakeUserRepository) OnFindUserByID(fns ...FindUserByIDFunc) {
for _, fn := range fns {
f.FFindUserByID = append(f.FFindUserByID, fn)
}
}
func (f *FakeUserRepository) OnCreateUser(fns ...CreateUserFunc) {
for _, fn := range fns {
f.FCreateUser = append(f.FCreateUser, fn)
}
}
func (f *FakeUserRepository) OnDeleteUserByID(fns ...DeleteUserByIDFunc) {
for _, fn := range fns {
f.FDeleteUserByID = append(f.FDeleteUserByID, fn)
}
}
func (f *FakeUserRepository) OnUpdateUser(fns ...UpdateUserFunc) {
for _, fn := range fns {
f.FUpdateUser = append(f.FUpdateUser, fn)
}
}
func (f *FakeUserRepository) OnExecute(fns ...ExecuteFunc) {
for _, fn := range fns {
f.FExecute = append(f.FExecute, fn)
}
}
func (f *FakeUserRepository) OnFindUserByName(fns ...FindUserByNameFunc) {
for _, fn := range fns {
f.FFindUserByName = append(f.FFindUserByName, fn)
}
}
func NewFakeUserRepository(t *testing.T, opts ...UserRepositoryOption) *FakeUserRepository {
f := &FakeUserRepository{t: t}
for _, opt := range opts {
opt(f)
}
t.Cleanup(func() {
if f.FindUserByIDCount != len(f.FFindUserByID) {
t.Fatalf("expected FindUserByID to be called %d times but got %d", len(f.FFindUserByID), f.FindUserByIDCount)
}
if f.CreateUserCount != len(f.FCreateUser) {
t.Fatalf("expected CreateUser to be called %d times but got %d", len(f.FCreateUser), f.CreateUserCount)
}
if f.DeleteUserByIDCount != len(f.FDeleteUserByID) {
t.Fatalf("expected DeleteUserByID to be called %d times but got %d", len(f.FDeleteUserByID), f.DeleteUserByIDCount)
}
if f.UpdateUserCount != len(f.FUpdateUser) {
t.Fatalf("expected UpdateUser to be called %d times but got %d", len(f.FUpdateUser), f.UpdateUserCount)
}
if f.ExecuteCount != len(f.FExecute) {
t.Fatalf("expected Execute to be called %d times but got %d", len(f.FExecute), f.ExecuteCount)
}
if f.FindUserByNameCount != len(f.FFindUserByName) {
t.Fatalf("expected FindUserByName to be called %d times but got %d", len(f.FFindUserByName), f.FindUserByNameCount)
}
})
return f
}
func (f *FakeUserRepository) FindUserByID(id int) (u User, err error) {
var idx = f.FindUserByIDCount
if f.FindUserByIDCount >= len(f.FFindUserByID) {
idx = len(f.FFindUserByID) - 1
}
if len(f.FFindUserByID) != 0 {
u, err := f.FFindUserByID[idx](id)
f.FindUserByIDCount++
return u, err
}
return User{}, nil
}
func (f *FakeUserRepository) CreateUser(user User) error {
var idx = f.CreateUserCount
if f.CreateUserCount >= len(f.FCreateUser) {
idx = len(f.FCreateUser) - 1
}
if len(f.FCreateUser) != 0 {
o1 := f.FCreateUser[idx](user)
f.CreateUserCount++
return o1
}
return nil
}
func (f *FakeUserRepository) DeleteUserByID(id int) error {
var idx = f.DeleteUserByIDCount
if f.DeleteUserByIDCount >= len(f.FDeleteUserByID) {
idx = len(f.FDeleteUserByID) - 1
}
if len(f.FDeleteUserByID) != 0 {
o1 := f.FDeleteUserByID[idx](id)
f.DeleteUserByIDCount++
return o1
}
return nil
}
func (f *FakeUserRepository) UpdateUser(old, new User) error {
var idx = f.UpdateUserCount
if f.UpdateUserCount >= len(f.FUpdateUser) {
idx = len(f.FUpdateUser) - 1
}
if len(f.FUpdateUser) != 0 {
o1 := f.FUpdateUser[idx](old, new)
f.UpdateUserCount++
return o1
}
return nil
}
func (f *FakeUserRepository) Execute(user User) error {
var idx = f.ExecuteCount
if f.ExecuteCount >= len(f.FExecute) {
idx = len(f.FExecute) - 1
}
if len(f.FExecute) != 0 {
o1 := f.FExecute[idx](user)
f.ExecuteCount++
return o1
}
return nil
}
func (f *FakeUserRepository) FindUserByName(name string) (User, error) {
var idx = f.FindUserByNameCount
if f.FindUserByNameCount >= len(f.FFindUserByName) {
idx = len(f.FFindUserByName) - 1
}
if len(f.FFindUserByName) != 0 {
o1, o2 := f.FFindUserByName[idx](name)
f.FindUserByNameCount++
return o1, o2
}
return User{}, nil
}
This can then be used in test as such
// Creating a New fake with *testing.T parameter
repo := repository.NewFakeUserRepository(t)
// Creating a New fake with already configured options
repo := repository.NewFakeUserRepository(t,
repository.OnCreateUser(func(user repository.User) error {
// Any checks on user or implementation details can go here
return nil
}),
)
// Creating a common implementation for specific usecase
f := repository.FindUserByIDFunc(func(id int) (u repository.User, err error) {
user, ok := userIDMap[id]
if !ok {
return repository.User{}, fmt.Errorf("unexpected user id")
}
return user, nil
})
// Setup number of expected calls 3 in this case
repo.OnFindUserByID(f, f, f)
Note the NewFakeUserRepository
method setups a t.Cleanup()
function to asses that all the functions where called the expected number of times.
t.Cleanup(func() {
if f.FindUserByIDCount != len(f.FFindUserByID) {
t.Fatalf("expected FindUserByID to be called %d times but got %d",
len(f.FFindUserByID),
f.FindUserByIDCount)
}
if f.CreateUserCount != len(f.FCreateUser) {
t.Fatalf("expected CreateUser to be called %d times but got %d",
len(f.FCreateUser),
f.CreateUserCount)
}
if f.DeleteUserByIDCount != len(f.FDeleteUserByID) {
t.Fatalf("expected DeleteUserByID to be called %d times but got %d",
len(f.FDeleteUserByID),
f.DeleteUserByIDCount)
}
if f.UpdateUserCount != len(f.FUpdateUser) {
t.Fatalf("expected UpdateUser to be called %d times but got %d",
len(f.FUpdateUser),
f.UpdateUserCount)
}
if f.ExecuteCount != len(f.FExecute) {
t.Fatalf("expected Execute to be called %d times but got %d",
len(f.FExecute),
f.ExecuteCount)
}
if f.FindUserByNameCount != len(f.FFindUserByName) {
t.Fatalf("expected FindUserByName to be called %d times but got %d",
len(f.FFindUserByName),
f.FindUserByNameCount)
}
})
This example and more can be found in the test directory.