Skip to content

zarldev/ffakes

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

17 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ffakes

License: MIT build

Introduction

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.

Installation

go install github.com/zarldev/ffakes@latest

Usage

 _____ _____ _____ __ ________ _____ 
/   __/   __/  _  |  |  /   __/  ___>
|   __|   __|  _  |  _ <|   __|___  |
\__/  \__/  \__|__|__|__\_____<_____/
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

Example

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.