Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom fakeable types #230

Merged
merged 15 commits into from Apr 4, 2023
32 changes: 32 additions & 0 deletions README.md
Expand Up @@ -168,6 +168,38 @@ fmt.Println(f.Created.String()) // 1908-12-07 04:14:25.685339029 +0000 UTC
// Nested Struct Fields and Embedded Fields
```

## Fakeable types

It is possible to extend a struct by implementing the `Fakeable` interface
in order to control the generation.

For example, this is useful when it is not possible to modify the struct that you want to fake by adding struct tags to a field but you still need to be able to control the generation process.

```go
// Imagine a CustomTime type that is needed to support a custom JSON Marshaler
type CustomTime time.Time

func (c *CustomTime) Fake(faker *gofakeit.Faker) interface{} {
return CustomTime(time.Now())
}

func (c *CustomTime) MarshalJSON() ([]byte, error) {
//...
}

// This is the struct that we cannot modify to add struct tags
type NotModifiable struct {
Token string
Creation *CustomTime
}

var f NotModifiable
gofakeit.Struct(&f)
fmt.Printf("%s", f.Token) // yvqqdH
fmt.Printf("%s", f.Creation) // 2023-04-02 23:00:00 +0000 UTC m=+0.000000001

```

## Custom Functions

In a lot of situations you may need to use your own random function usage for your specific needs.
Expand Down
80 changes: 80 additions & 0 deletions fakeable.go
@@ -0,0 +1,80 @@
package gofakeit

import (
"errors"
"fmt"
"reflect"
)

// Fakeable is an interface that can be implemented by a type to provide a custom fake value.
type Fakeable interface {
// Fake returns a fake value for the type.
Fake(faker *Faker) interface{}
}

func isFakeable(t reflect.Type) bool {
fakeableTyp := reflect.TypeOf((*Fakeable)(nil)).Elem()

return t.Implements(fakeableTyp) || reflect.PtrTo(t).Implements(fakeableTyp)
}

func callFake(faker *Faker, v reflect.Value, possibleKinds ...reflect.Kind) (interface{}, error) {
f, ok := v.Addr().Interface().(Fakeable)
if !ok {
return nil, errors.New("not a Fakeable type")
}

fakedValue := f.Fake(faker)
k := reflect.TypeOf(fakedValue).Kind()
if !containsKind(possibleKinds, k) {
return nil, fmt.Errorf("returned value kind %q is not amongst the valid ones: %v", k, possibleKinds)
}

switch k {
case reflect.String:
return reflect.ValueOf(fakedValue).String(), nil
case reflect.Bool:
return reflect.ValueOf(fakedValue).Bool(), nil
case reflect.Int:
return int(reflect.ValueOf(fakedValue).Int()), nil
case reflect.Int8:
return int8(reflect.ValueOf(fakedValue).Int()), nil
case reflect.Int16:
return int16(reflect.ValueOf(fakedValue).Int()), nil
case reflect.Int32:
return int32(reflect.ValueOf(fakedValue).Int()), nil
case reflect.Int64:
return int64(reflect.ValueOf(fakedValue).Int()), nil
case reflect.Uint:
return uint(reflect.ValueOf(fakedValue).Uint()), nil
case reflect.Uint8:
return uint8(reflect.ValueOf(fakedValue).Uint()), nil
case reflect.Uint16:
return uint16(reflect.ValueOf(fakedValue).Uint()), nil
case reflect.Uint32:
return uint32(reflect.ValueOf(fakedValue).Uint()), nil
case reflect.Uint64:
return uint64(reflect.ValueOf(fakedValue).Uint()), nil
case reflect.Float32:
return float32(reflect.ValueOf(fakedValue).Float()), nil
case reflect.Float64:
return float64(reflect.ValueOf(fakedValue).Float()), nil
case reflect.Slice:
return reflect.ValueOf(fakedValue).Interface(), nil
case reflect.Map:
return reflect.ValueOf(fakedValue).Interface(), nil
case reflect.Struct:
return reflect.ValueOf(fakedValue).Interface(), nil
default:
return nil, fmt.Errorf("unsupported type %q", k)
}
}

func containsKind(possibleKinds []reflect.Kind, kind reflect.Kind) bool {
for _, k := range possibleKinds {
if k == kind {
return true
}
}
return false
}