Skip to content

Commit

Permalink
Merge pull request #230 from mgilbir/custom-fakeable-types
Browse files Browse the repository at this point in the history
Custom fakeable types
  • Loading branch information
brianvoe committed Apr 4, 2023
2 parents af9e266 + 53abad6 commit bdec0d0
Show file tree
Hide file tree
Showing 6 changed files with 1,246 additions and 141 deletions.
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
}

0 comments on commit bdec0d0

Please sign in to comment.