diff --git a/README.md b/README.md index 05c27fa9..b7410683 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/fakeable.go b/fakeable.go new file mode 100644 index 00000000..f963556c --- /dev/null +++ b/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 +} diff --git a/fakeable_external_test.go b/fakeable_external_test.go new file mode 100644 index 00000000..b30cb6e9 --- /dev/null +++ b/fakeable_external_test.go @@ -0,0 +1,729 @@ +package gofakeit_test + +import ( + "fmt" + "math/rand" + "testing" + "time" + + "github.com/brianvoe/gofakeit/v6" +) + +var ( + testTimeValue = time.Now() +) + +type CustomString string + +func (c CustomString) Fake(faker *gofakeit.Faker) interface{} { + return CustomString("hello test") +} + +type CustomBool bool + +func (c CustomBool) Fake(faker *gofakeit.Faker) interface{} { + return CustomBool(true) +} + +type CustomInt int + +func (c CustomInt) Fake(faker *gofakeit.Faker) interface{} { + return CustomInt(-42) +} + +type CustomInt8 int8 + +func (c CustomInt8) Fake(faker *gofakeit.Faker) interface{} { + return CustomInt8(-42) +} + +type CustomInt16 int16 + +func (c CustomInt16) Fake(faker *gofakeit.Faker) interface{} { + return CustomInt16(-42) +} + +type CustomInt32 int32 + +func (c CustomInt32) Fake(faker *gofakeit.Faker) interface{} { + return CustomInt32(-42) +} + +type CustomInt64 int64 + +func (c CustomInt64) Fake(faker *gofakeit.Faker) interface{} { + return CustomInt64(-42) +} + +type CustomUint uint + +func (c CustomUint) Fake(faker *gofakeit.Faker) interface{} { + return CustomUint(42) +} + +type CustomUint8 uint8 + +func (c CustomUint8) Fake(faker *gofakeit.Faker) interface{} { + return CustomUint8(42) +} + +type CustomUint16 uint16 + +func (c CustomUint16) Fake(faker *gofakeit.Faker) interface{} { + return CustomUint16(42) +} + +type CustomUint32 uint32 + +func (c CustomUint32) Fake(faker *gofakeit.Faker) interface{} { + return CustomUint32(42) +} + +type CustomUint64 uint64 + +func (c CustomUint64) Fake(faker *gofakeit.Faker) interface{} { + return CustomUint64(42) +} + +type CustomFloat32 float32 + +func (c CustomFloat32) Fake(faker *gofakeit.Faker) interface{} { + return CustomFloat32(42.123) +} + +type CustomFloat64 float64 + +func (c CustomFloat64) Fake(faker *gofakeit.Faker) interface{} { + return CustomFloat64(42.123) +} + +type CustomTime time.Time + +func (c *CustomTime) Fake(faker *gofakeit.Faker) interface{} { + return CustomTime(testTimeValue) +} + +func (c CustomTime) String() string { + return time.Time(c).String() +} + +type CustomSlice []string + +func (c CustomSlice) Fake(faker *gofakeit.Faker) interface{} { + return CustomSlice([]string{"hello", "test"}) +} + +type CustomMap map[string]string + +func (c CustomMap) Fake(faker *gofakeit.Faker) interface{} { + return CustomMap(map[string]string{"hello": "1", "test": "2"}) +} + +type CustomStruct struct { + Str string + Int int +} + +func (c CustomStruct) Fake(faker *gofakeit.Faker) interface{} { + return CustomStruct{ + Str: "hello test", + Int: 42, + } +} + +type NestedCustom struct { + Str CustomString + PtrStr *CustomString + Bool CustomBool + Int CustomInt + Int8 CustomInt8 + Int16 CustomInt16 + Int32 CustomInt32 + Int64 CustomInt64 + Uint CustomUint + Uint8 CustomUint8 + Uint16 CustomUint16 + Uint32 CustomUint32 + Uint64 CustomUint64 + Float32 CustomFloat32 + Float64 CustomFloat64 + Timestamp CustomTime + PtrTimestamp *CustomTime + SliceStr CustomSlice + MapStr CustomMap + Struct CustomStruct + PtrStruct *CustomStruct +} + +type NestedOverrideCustom struct { + Str CustomString `fake:"{name}"` + PtrStr *CustomString `fake:"{name}"` + Bool CustomBool `fake:"false"` + Int CustomInt `fake:"{number:-10,1000}"` + Int8 CustomInt8 `fake:"{number:-10,1000}"` + Int16 CustomInt16 `fake:"{number:-10,1000}"` + Int32 CustomInt32 `fake:"{number:-10,1000}"` + Int64 CustomInt64 `fake:"{number:-10,1000}"` + Uint CustomUint `fake:"{number:100,1000}"` + Uint8 CustomUint8 `fake:"{number:100,1000}"` + Uint16 CustomUint16 `fake:"{number:100,1000}"` + Uint32 CustomUint32 `fake:"{number:100,1000}"` + Uint64 CustomUint64 `fake:"{number:100,1000}"` + Float32 CustomFloat32 `fake:"{number:100,1000}"` + Float64 CustomFloat64 `fake:"{number:100,1000}"` + Timestamp CustomTime `fake:"{raw_test_date}"` + PtrTimestamp *CustomTime `fake:"{raw_test_date}"` + SliceStr CustomSlice `fake:"{word}"` + MapStr CustomMap `fakesize:"2"` +} + +func TestCustomString(t *testing.T) { + var d CustomString + err := gofakeit.Struct(&d) + if err != nil { + t.Fatal(err) + } + + expected := "hello test" + if d != CustomString(expected) { + t.Errorf("expected %q, got %q", expected, d) + } +} + +func TestCustomBool(t *testing.T) { + var d CustomBool + err := gofakeit.Struct(&d) + if err != nil { + t.Fatal(err) + } + + expected := true + if d != CustomBool(expected) { + t.Errorf("expected %t, got %t", expected, d) + } +} + +func TestCustomInt(t *testing.T) { + var d CustomInt + err := gofakeit.Struct(&d) + if err != nil { + t.Fatal(err) + } + + expected := -42 + if d != CustomInt(expected) { + t.Errorf("expected %d, got %d", expected, d) + } +} + +func TestCustomInt8(t *testing.T) { + var d CustomInt8 + err := gofakeit.Struct(&d) + if err != nil { + t.Fatal(err) + } + + expected := -42 + if d != CustomInt8(expected) { + t.Errorf("expected %d, got %d", expected, d) + } +} + +func TestCustomInt16(t *testing.T) { + var d CustomInt16 + err := gofakeit.Struct(&d) + if err != nil { + t.Fatal(err) + } + + expected := -42 + if d != CustomInt16(expected) { + t.Errorf("expected %d, got %d", expected, d) + } +} + +func TestCustomInt32(t *testing.T) { + var d CustomInt32 + err := gofakeit.Struct(&d) + if err != nil { + t.Fatal(err) + } + + expected := -42 + if d != CustomInt32(expected) { + t.Errorf("expected %d, got %d", expected, d) + } +} + +func TestCustomInt64(t *testing.T) { + var d CustomInt64 + err := gofakeit.Struct(&d) + if err != nil { + t.Fatal(err) + } + + expected := -42 + if d != CustomInt64(expected) { + t.Errorf("expected %d, got %d", expected, d) + } +} + +func TestCustomUint(t *testing.T) { + var d CustomUint + err := gofakeit.Struct(&d) + if err != nil { + t.Fatal(err) + } + + expected := 42 + if d != CustomUint(expected) { + t.Errorf("expected %d, got %d", expected, d) + } +} + +func TestCustomUint8(t *testing.T) { + var d CustomUint8 + err := gofakeit.Struct(&d) + if err != nil { + t.Fatal(err) + } + + expected := 42 + if d != CustomUint8(expected) { + t.Errorf("expected %d, got %d", expected, d) + } +} + +func TestCustomUint16(t *testing.T) { + var d CustomUint16 + err := gofakeit.Struct(&d) + if err != nil { + t.Fatal(err) + } + + expected := 42 + if d != CustomUint16(expected) { + t.Errorf("expected %d, got %d", expected, d) + } +} + +func TestCustomUint32(t *testing.T) { + var d CustomUint32 + err := gofakeit.Struct(&d) + if err != nil { + t.Fatal(err) + } + + expected := 42 + if d != CustomUint32(expected) { + t.Errorf("expected %d, got %d", expected, d) + } +} + +func TestCustomUint64(t *testing.T) { + var d CustomUint64 + err := gofakeit.Struct(&d) + if err != nil { + t.Fatal(err) + } + + expected := 42 + if d != CustomUint64(expected) { + t.Errorf("expected %d, got %d", expected, d) + } +} +func TestCustomFloat32(t *testing.T) { + var d CustomFloat32 + err := gofakeit.Struct(&d) + if err != nil { + t.Fatal(err) + } + + expected := 42.123 + if d != CustomFloat32(expected) { + t.Errorf("expected %f, got %f", expected, d) + } +} + +func TestCustomFloat64(t *testing.T) { + var d CustomFloat64 + err := gofakeit.Struct(&d) + if err != nil { + t.Fatal(err) + } + + expected := 42.123 + if d != CustomFloat64(expected) { + t.Errorf("expected %f, got %f", expected, d) + } +} + +func TestCustomTime(t *testing.T) { + var d CustomTime + err := gofakeit.Struct(&d) + if err != nil { + t.Fatal(err) + } + + expected := testTimeValue + if d != CustomTime(expected) { + t.Errorf("expected %q, got %q", expected.String(), d.String()) + } +} + +func TestCustomTimePtr(t *testing.T) { + var d *CustomTime + err := gofakeit.Struct(&d) + if err != nil { + t.Fatal(err) + } + + expected := testTimeValue + if d == nil { + t.Fatal("expected a pointer to a CustomTime, got nil") + } + if *d != CustomTime(expected) { + t.Errorf("expected %q, got %q", expected.String(), d.String()) + } +} + +func TestCustomSlice(t *testing.T) { + var d CustomSlice + err := gofakeit.Struct(&d) + if err != nil { + t.Fatal(err) + } + + expected := []string{"hello", "test"} + if len(d) != len(expected) { + t.Fatalf("expected %v, got %v", expected, d) + } + for i, v := range expected { + if d[i] != v { + t.Errorf("expected item %d of the slice to be: %v, got %v", i, expected[i], d[i]) + } + } +} + +func TestCustomMap(t *testing.T) { + var d CustomMap + err := gofakeit.Struct(&d) + if err != nil { + t.Fatal(err) + } + + expected := map[string]string{"hello": "1", "test": "2"} + if len(d) != len(expected) { + t.Fatalf("expected %v, got %v", expected, d) + } + for k, v := range expected { + if d[k] != v { + t.Errorf("expected item %v of the slice to be: %v, got %v", k, expected[k], d[k]) + } + } +} + +func TestCustomStruct(t *testing.T) { + var d CustomStruct + err := gofakeit.Struct(&d) + if err != nil { + t.Fatal(err) + } + + if d.Str != "hello test" { + t.Errorf("expected %q, got %q", "hello test", d.Str) + } + if d.Int != 42 { + t.Errorf("expected %d, got %d", 42, d.Int) + } +} + +func TestNestedCustom(t *testing.T) { + var d NestedCustom + err := gofakeit.Struct(&d) + if err != nil { + t.Fatal(err) + } + + expectedStr := "hello test" + if d.Str != CustomString(expectedStr) { + t.Errorf("Str: expected %q, got %q", expectedStr, d.Str) + } + + if *d.PtrStr != CustomString(expectedStr) { + t.Errorf("Str: expected %q, got %q", expectedStr, *d.PtrStr) + } + + if !d.Bool { + t.Errorf("Bool: expected true, got false") + } + + expectedInt := -42 + if d.Int != CustomInt(expectedInt) { + t.Errorf("Int: expected %d, got %d", expectedInt, d.Int) + } + if d.Int8 != CustomInt8(expectedInt) { + t.Errorf("Int: expected %d, got %d", expectedInt, d.Int8) + } + if d.Int16 != CustomInt16(expectedInt) { + t.Errorf("Int: expected %d, got %d", expectedInt, d.Int16) + } + if d.Int32 != CustomInt32(expectedInt) { + t.Errorf("Int: expected %d, got %d", expectedInt, d.Int32) + } + if d.Int64 != CustomInt64(expectedInt) { + t.Errorf("Int: expected %d, got %d", expectedInt, d.Int64) + } + + expectedUint := uint(42) + if d.Uint != CustomUint(expectedUint) { + t.Errorf("Uint: expected %d, got %d", expectedUint, d.Uint) + } + if d.Uint8 != CustomUint8(expectedUint) { + t.Errorf("Uint: expected %d, got %d", expectedUint, d.Uint8) + } + if d.Uint16 != CustomUint16(expectedUint) { + t.Errorf("Uint: expected %d, got %d", expectedUint, d.Uint16) + } + if d.Uint32 != CustomUint32(expectedUint) { + t.Errorf("Uint: expected %d, got %d", expectedUint, d.Uint32) + } + if d.Uint64 != CustomUint64(expectedUint) { + t.Errorf("Uint: expected %d, got %d", expectedUint, d.Uint64) + } + + expectedFloat := 42.123 + if d.Float32 != CustomFloat32(expectedFloat) { + t.Errorf("Float: expected %f, got %f", expectedFloat, d.Float32) + } + if d.Float64 != CustomFloat64(expectedFloat) { + t.Errorf("Float: expected %f, got %f", expectedFloat, d.Float64) + } + + expectedSlice := []string{"hello", "test"} + if len(d.SliceStr) != len(expectedSlice) { + t.Fatalf("expected %v, got %v", expectedSlice, d.SliceStr) + } + for i, v := range expectedSlice { + if d.SliceStr[i] != v { + t.Errorf("expected item %d of the slice to be: %v, got %v", i, expectedSlice[i], d.SliceStr[i]) + } + } + + expectedMap := map[string]string{"hello": "1", "test": "2"} + if len(d.MapStr) != len(expectedMap) { + t.Fatalf("expected %v, got %v", expectedMap, d) + } + for k, v := range expectedMap { + if d.MapStr[k] != v { + t.Errorf("expected item %v of the map to be: %v, got %v", k, expectedMap[k], d.MapStr[k]) + } + } + + if d.Struct.Str != "hello test" { + t.Errorf("expected %q, got %q", "hello test", d.Struct.Str) + } + if d.Struct.Int != 42 { + t.Errorf("expected %d, got %d", 42, d.Struct.Int) + } + + if d.PtrStruct == nil { + t.Fatal("expected PtrStruct to not be nil") + } + + if d.PtrStruct.Str != "hello test" { + t.Errorf("expected %q, got %q", "hello test", d.PtrStruct.Str) + } + if d.PtrStruct.Int != 42 { + t.Errorf("expected %d, got %d", 42, d.PtrStruct.Int) + } + + expectedTimestamp := testTimeValue + if d.Timestamp != CustomTime(expectedTimestamp) { + t.Errorf("expected %q, got %q", expectedTimestamp.String(), d.Timestamp.String()) + } + + if d.PtrTimestamp == nil { + t.Fatal("expected a pointer to a CustomTime, got nil") + } + if *d.PtrTimestamp != CustomTime(expectedTimestamp) { + t.Errorf("expected %q, got %q", expectedTimestamp.String(), d.PtrTimestamp.String()) + } +} + +func TestNestedOverrideCustom(t *testing.T) { + gofakeit.AddFuncLookup("raw_test_date", gofakeit.Info{ + Display: "Date", + Category: "time", + Description: "Random date", + Example: "2006-01-02T15:04:05Z07:00", + Output: "time.Time", + Params: []gofakeit.Param{ + { + Field: "format", + Display: "Format", + Type: "time.Time", + Description: "Raw date time.Time object", + }, + }, + Generate: func(r *rand.Rand, m *gofakeit.MapParams, info *gofakeit.Info) (interface{}, error) { + return gofakeit.Date(), nil + }, + }) + + defer gofakeit.RemoveFuncLookup("raw_test_date") + + var d NestedOverrideCustom + err := gofakeit.Struct(&d) + if err != nil { + t.Fatal(err) + } + + nonOverrideStr := "hello test" + if d.Str == CustomString(nonOverrideStr) { + t.Errorf("Str: expected a random string but got the non-overriden value") + } + + if *d.PtrStr == CustomString(nonOverrideStr) { + t.Errorf("PtrStr: expected a random string but got the non-overriden value") + } + + if d.Bool { + t.Errorf("Bool: expected false, got true") + } + + nonOverrideInt := -42 + if d.Int == CustomInt(nonOverrideInt) { + t.Errorf("Int: expected a random integer but got the non-overriden value") + } + if d.Int8 == CustomInt8(nonOverrideInt) { + t.Errorf("Int: expected a random integer but got the non-overriden value") + } + if d.Int16 == CustomInt16(nonOverrideInt) { + t.Errorf("Int: expected a random integer but got the non-overriden value") + } + if d.Int32 == CustomInt32(nonOverrideInt) { + t.Errorf("Int: expected a random integer but got the non-overriden value") + } + if d.Int64 == CustomInt64(nonOverrideInt) { + t.Errorf("Int: expected a random integer but got the non-overriden value") + } + + nonOverrideUint := uint(42) + if d.Uint == CustomUint(nonOverrideUint) { + t.Errorf("Uint: expected a random unsigned integer but got the non-overriden value") + } + if d.Uint8 == CustomUint8(nonOverrideUint) { + t.Errorf("Uint: expected a random unsigned integer but got the non-overriden value") + } + if d.Uint16 == CustomUint16(nonOverrideUint) { + t.Errorf("Uint: expected a random unsigned integer but got the non-overriden value") + } + if d.Uint32 == CustomUint32(nonOverrideUint) { + t.Errorf("Uint: expected a random unsigned integer but got the non-overriden value") + } + if d.Uint64 == CustomUint64(nonOverrideUint) { + t.Errorf("Uint: expected a random unsigned integer but got the non-overriden value") + } + + nonOverrideFloat := 42.123 + if d.Float32 == CustomFloat32(nonOverrideFloat) { + t.Errorf("Float: expected a random unsigned integer but got the non-overriden value") + } + if d.Float64 == CustomFloat64(nonOverrideFloat) { + t.Errorf("Uint: expected a random unsigned integer but got the non-overriden value") + } + + nonOverrideSlice := []string{"hello", "test"} + if len(d.SliceStr) == len(nonOverrideSlice) { + t.Logf("Slice: Got the same length as the non-overriden slice: %v vs %v", nonOverrideSlice, d.SliceStr) + for i, v := range nonOverrideSlice { + if d.SliceStr[i] == v { + t.Errorf("Slice: Got non-overriden item %d in the slice", i) + } + } + } + + nonOverrideMap := map[string]string{"hello": "1", "test": "2"} + if len(d.MapStr) == len(nonOverrideMap) { + t.Logf("Map: Got the same length as the non-overriden map: %v vs %v", nonOverrideMap, d.MapStr) + + for k, v := range nonOverrideMap { + if d.MapStr[k] == v { + t.Errorf("Map: Got non-overriden item %v in the slice", k) + } + } + } +} + +func TestSliceCustom(t *testing.T) { + var B []CustomString + gofakeit.Slice(&B) + + if len(B) == 0 { + t.Errorf("expected slice to not be empty") + } + + expected := CustomString("hello test") + for _, v := range B { + if v != expected { + t.Errorf("expected all items to be %q, got %q", expected, v) + } + } +} + +func TestSliceNestedCustom(t *testing.T) { + var B []NestedCustom + gofakeit.Slice(&B) + + if len(B) == 0 { + t.Errorf("expected slice to not be empty") + } + + expected := CustomString("hello test") + for _, v := range B { + if v.Str != expected { + t.Fatalf("expected all items to be %q, got %q", expected, v.Str) + } + } +} + +func ExampleCustomInt() { + f1 := gofakeit.New(10) + f2 := gofakeit.New(100) + + var A1 CustomInt + var A2 CustomInt + // CustomInt always returns -42 independently of the seed + f1.Struct(&A1) + f2.Struct(&A2) + + fmt.Println(A1) + fmt.Println(A2) + // Output: + // -42 + // -42 +} + +type EvenInt int + +func (e EvenInt) Fake(faker *gofakeit.Faker) interface{} { + return EvenInt(faker.Int8() * 2) +} + +func ExampleEvenInt() { + f1 := gofakeit.New(10) + f2 := gofakeit.New(100) + + var E1 EvenInt + var E2 EvenInt + // EventInt always returns an even number + f1.Struct(&E1) + f2.Struct(&E2) + + fmt.Println(E1) + fmt.Println(E2) + // Output: + // 6 + // -92 +} diff --git a/fakeable_test.go b/fakeable_test.go new file mode 100644 index 00000000..4496c713 --- /dev/null +++ b/fakeable_test.go @@ -0,0 +1,164 @@ +package gofakeit + +import ( + "fmt" + "math" + "reflect" + "testing" +) + +type strTyp string + +func (t strTyp) Fake(faker *Faker) interface{} { + return faker.FirstName() +} + +type strTypPtr string + +func (t *strTypPtr) Fake(faker *Faker) interface{} { + return strTypPtr("hello test ptr") +} + +type testStruct1 struct { + B string `fake:"{firstname}"` +} + +type testStruct2 struct { + B strTyp +} + +func TestIsFakeable(t *testing.T) { + var t1 testStruct2 + var t2 *testStruct2 + var t3 strTyp + var t4 *strTyp + var t5 strTypPtr + var t6 *strTypPtr + + if isFakeable(reflect.ValueOf(t1).Type()) { + t.Errorf("expected testStruct2 not to be fakeable") + } + + if isFakeable(reflect.ValueOf(t2).Type()) { + t.Errorf("expected *testStruct2 not to be fakeable") + } + + if !isFakeable(reflect.ValueOf(t3).Type()) { + t.Errorf("expected strTyp to be fakeable") + } + + if !isFakeable(reflect.ValueOf(t4).Type()) { + t.Errorf("expected *strTyp to be fakeable") + } + + if !isFakeable(reflect.ValueOf(t5).Type()) { + t.Errorf("expected strTypPtr to be fakeable") + } + + if !isFakeable(reflect.ValueOf(t6).Type()) { + t.Errorf("expected *strTypPtr to be fakeable") + } +} + +func ExampleFakeable() { + var t1 testStruct1 + var t2 testStruct1 + var t3 testStruct2 + var t4 testStruct2 + New(314).Struct(&t1) + New(314).Struct(&t2) + New(314).Struct(&t3) + New(314).Struct(&t4) + + fmt.Printf("%#v\n", t1) + fmt.Printf("%#v\n", t2) + fmt.Printf("%#v\n", t3) + fmt.Printf("%#v\n", t4) + // Expected Output: + // gofakeit.testStruct1{B:"Margarette"} + // gofakeit.testStruct1{B:"Margarette"} + // gofakeit.testStruct2{B:"Margarette"} + // gofakeit.testStruct2{B:"Margarette"} +} + +type gammaFloat64 float64 + +func (gammaFloat64) Fake(faker *Faker) interface{} { + alpha := 2.0 + + // Generate a random value from the Gamma distribution + var r float64 + for r == 0 { + u := faker.Float64Range(0, 1) + v := faker.Float64Range(0, 1) + w := u * (1 - u) + y := math.Sqrt(-2 * math.Log(w) / w) + x := alpha * (y*v + u - 0.5) + if x > 0 { + r = x + } + } + return gammaFloat64(r) +} + +func ExampleGammaFloat64() { + f1 := New(100) + + // Fakes random values from the Gamma distribution + var A1 gammaFloat64 + var A2 gammaFloat64 + var A3 gammaFloat64 + f1.Struct(&A1) + f1.Struct(&A2) + f1.Struct(&A3) + + fmt.Println(A1) + fmt.Println(A2) + fmt.Println(A3) + // Output: + // 10.300651760129734 + // 5.391434877284098 + // 2.0575989252140676 +} + +type poissonInt64 int64 + +func (poissonInt64) Fake(faker *Faker) interface{} { + lambda := 15.0 + + // Generate a random value from the Poisson distribution + var k int64 + var p float64 = 1.0 + var L float64 = math.Exp(-lambda) + for p > L { + u := faker.Float64Range(0, 1) + p *= u + k++ + } + return poissonInt64(k - 1) +} + +type customerSupportEmployee struct { + Name string `fake:"{firstname} {lastname}"` + CallCountPerHour poissonInt64 +} + +func ExamplecustomerSupportEmployee() { + f1 := New(100) + + // Fakes random values from the Gamma distribution + var A1 customerSupportEmployee + var A2 customerSupportEmployee + var A3 customerSupportEmployee + f1.Struct(&A1) + f1.Struct(&A2) + f1.Struct(&A3) + + fmt.Printf("%#v\n", A1) + fmt.Printf("%#v\n", A2) + fmt.Printf("%#v\n", A3) + // Output: + // gofakeit.customerSupportEmployee{Name:"Pearline Rippin", CallCountPerHour:12} + // gofakeit.customerSupportEmployee{Name:"Sammie Renner", CallCountPerHour:23} + // gofakeit.customerSupportEmployee{Name:"Katlyn Runte", CallCountPerHour:8} +} diff --git a/slice.go b/slice.go index 5f93fa5b..b225abc4 100644 --- a/slice.go +++ b/slice.go @@ -1,16 +1,15 @@ package gofakeit import ( - "math/rand" "reflect" ) // Slice fills built-in types and exported fields of a struct with random data. -func Slice(v interface{}) { sliceFunc(globalFaker.Rand, v) } +func Slice(v interface{}) { sliceFunc(globalFaker, v) } // Slice fills built-in types and exported fields of a struct with random data. -func (f *Faker) Slice(v interface{}) { sliceFunc(f.Rand, v) } +func (f *Faker) Slice(v interface{}) { sliceFunc(f, v) } -func sliceFunc(ra *rand.Rand, v interface{}) { - r(ra, reflect.TypeOf(v), reflect.ValueOf(v), "", -1) +func sliceFunc(f *Faker, v interface{}) { + r(f, reflect.TypeOf(v), reflect.ValueOf(v), "", -1) } diff --git a/struct.go b/struct.go index 33ec1510..f97ebecc 100644 --- a/struct.go +++ b/struct.go @@ -2,7 +2,6 @@ package gofakeit import ( "errors" - "math/rand" "reflect" "strconv" "strings" @@ -10,49 +9,51 @@ import ( ) // Struct fills in exported fields of a struct with random data -// based on the value of `fake` tag of exported fields. +// based on the value of `fake` tag of exported fields +// or with the result of a call to the Fake() method +// if the field type implements `Fakeable`. // Use `fake:"skip"` to explicitly skip an element. // All built-in types are supported, with templating support // for string types. -func Struct(v interface{}) error { return structFunc(globalFaker.Rand, v) } +func Struct(v interface{}) error { return structFunc(globalFaker, v) } // Struct fills in exported fields of a struct with random data // based on the value of `fake` tag of exported fields. // Use `fake:"skip"` to explicitly skip an element. // All built-in types are supported, with templating support // for string types. -func (f *Faker) Struct(v interface{}) error { return structFunc(f.Rand, v) } +func (f *Faker) Struct(v interface{}) error { return structFunc(f, v) } -func structFunc(ra *rand.Rand, v interface{}) error { - return r(ra, reflect.TypeOf(v), reflect.ValueOf(v), "", 0) +func structFunc(f *Faker, v interface{}) error { + return r(f, reflect.TypeOf(v), reflect.ValueOf(v), "", 0) } -func r(ra *rand.Rand, t reflect.Type, v reflect.Value, tag string, size int) error { +func r(f *Faker, t reflect.Type, v reflect.Value, tag string, size int) error { switch t.Kind() { case reflect.Ptr: - return rPointer(ra, t, v, tag, size) + return rPointer(f, t, v, tag, size) case reflect.Struct: - return rStruct(ra, t, v, tag) + return rStruct(f, t, v, tag) case reflect.String: - return rString(ra, v, tag) + return rString(f, t, v, tag) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - return rUint(ra, t, v, tag) + return rUint(f, t, v, tag) case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return rInt(ra, t, v, tag) + return rInt(f, t, v, tag) case reflect.Float32, reflect.Float64: - return rFloat(ra, t, v, tag) + return rFloat(f, t, v, tag) case reflect.Bool: - return rBool(ra, v, tag) + return rBool(f, t, v, tag) case reflect.Array, reflect.Slice: - return rSlice(ra, t, v, tag, size) + return rSlice(f, t, v, tag, size) case reflect.Map: - return rMap(ra, t, v, tag, size) + return rMap(f, t, v, tag, size) } return nil } -func rCustom(ra *rand.Rand, t reflect.Type, v reflect.Value, tag string) error { +func rCustom(f *Faker, t reflect.Type, v reflect.Value, tag string) error { // If tag is empty return error if tag == "" { return errors.New("tag is empty") @@ -65,7 +66,7 @@ func rCustom(ra *rand.Rand, t reflect.Type, v reflect.Value, tag string) error { mapParams := parseMapParams(info, fParams) // Call function - fValue, err := info.Generate(ra, mapParams, info) + fValue, err := info.Generate(f.Rand, mapParams, info) if err != nil { return err } @@ -88,7 +89,7 @@ func rCustom(ra *rand.Rand, t reflect.Type, v reflect.Value, tag string) error { } // Set the value - v.Set(fieldElem) + v.Set(fieldElem.Convert(v.Type())) // If a function is called to set the struct // stop from going through sub fields @@ -98,70 +99,78 @@ func rCustom(ra *rand.Rand, t reflect.Type, v reflect.Value, tag string) error { return errors.New("function not found") } -func rStruct(ra *rand.Rand, t reflect.Type, v reflect.Value, tag string) error { +func rStruct(f *Faker, t reflect.Type, v reflect.Value, tag string) error { // Check if tag exists, if so run custom function if t.Name() != "" && tag != "" { - return rCustom(ra, t, v, tag) - } + return rCustom(f, t, v, tag) + } else if isFakeable(t) { + value, err := callFake(f, v, reflect.Struct) + if err != nil { + return err + } - n := t.NumField() - for i := 0; i < n; i++ { - elementT := t.Field(i) - elementV := v.Field(i) - fakeTag, ok := elementT.Tag.Lookup("fake") + v.Set(reflect.ValueOf(value)) + } else { - // Check whether or not to skip this field - if ok && fakeTag == "skip" { - // Do nothing, skip it - continue - } + n := t.NumField() + for i := 0; i < n; i++ { + elementT := t.Field(i) + elementV := v.Field(i) + fakeTag, ok := elementT.Tag.Lookup("fake") - // Check to make sure you can set it or that its an embeded(anonymous) field - if elementV.CanSet() || elementT.Anonymous { - // Check if reflect type is of values we can specifically set - switch elementT.Type.String() { - case "time.Time": - err := rTime(ra, elementT, elementV, fakeTag) - if err != nil { - return err - } + // Check whether or not to skip this field + if ok && fakeTag == "skip" { + // Do nothing, skip it continue } - // Check if fakesize is set - size := -1 // Set to -1 to indicate fakesize was not set - fs, ok := elementT.Tag.Lookup("fakesize") - if ok { - var err error - - // Check if size has params separated by , - if strings.Contains(fs, ",") { - sizeSplit := strings.SplitN(fs, ",", 2) - if len(sizeSplit) == 2 { - var sizeMin int - var sizeMax int + // Check to make sure you can set it or that its an embeded(anonymous) field + if elementV.CanSet() || elementT.Anonymous { + // Check if reflect type is of values we can specifically set + switch elementT.Type.String() { + case "time.Time": + err := rTime(f, elementT, elementV, fakeTag) + if err != nil { + return err + } + continue + } - sizeMin, err = strconv.Atoi(sizeSplit[0]) - if err != nil { - return err + // Check if fakesize is set + size := -1 // Set to -1 to indicate fakesize was not set + fs, ok := elementT.Tag.Lookup("fakesize") + if ok { + var err error + + // Check if size has params separated by , + if strings.Contains(fs, ",") { + sizeSplit := strings.SplitN(fs, ",", 2) + if len(sizeSplit) == 2 { + var sizeMin int + var sizeMax int + + sizeMin, err = strconv.Atoi(sizeSplit[0]) + if err != nil { + return err + } + sizeMax, err = strconv.Atoi(sizeSplit[1]) + if err != nil { + return err + } + + size = f.Rand.Intn(sizeMax-sizeMin+1) + sizeMin } - sizeMax, err = strconv.Atoi(sizeSplit[1]) + } else { + size, err = strconv.Atoi(fs) if err != nil { return err } - - size = ra.Intn(sizeMax-sizeMin+1) + sizeMin - } - } else { - size, err = strconv.Atoi(fs) - if err != nil { - return err } } - } - err := r(ra, elementT.Type, elementV, fakeTag, size) - if err != nil { - return err + err := r(f, elementT.Type, elementV, fakeTag, size) + if err != nil { + return err + } } } } @@ -169,17 +178,18 @@ func rStruct(ra *rand.Rand, t reflect.Type, v reflect.Value, tag string) error { return nil } -func rPointer(ra *rand.Rand, t reflect.Type, v reflect.Value, tag string, size int) error { +func rPointer(f *Faker, t reflect.Type, v reflect.Value, tag string, size int) error { elemT := t.Elem() if v.IsNil() { - nv := reflect.New(elemT) - err := r(ra, elemT, nv.Elem(), tag, size) + nv := reflect.New(elemT).Elem() + err := r(f, elemT, nv, tag, size) if err != nil { return err } - v.Set(nv) + + v.Set(nv.Addr()) } else { - err := r(ra, elemT, v.Elem(), tag, size) + err := r(f, elemT, v.Elem(), tag, size) if err != nil { return err } @@ -188,7 +198,7 @@ func rPointer(ra *rand.Rand, t reflect.Type, v reflect.Value, tag string, size i return nil } -func rSlice(ra *rand.Rand, t reflect.Type, v reflect.Value, tag string, size int) error { +func rSlice(f *Faker, t reflect.Type, v reflect.Value, tag string, size int) error { // If you cant even set it dont even try if !v.CanSet() { return errors.New("cannot set slice") @@ -197,10 +207,18 @@ func rSlice(ra *rand.Rand, t reflect.Type, v reflect.Value, tag string, size int // Check if tag exists, if so run custom function if t.Name() != "" && tag != "" { // Check to see if custom function works if not continue to normal loop of values - err := rCustom(ra, t, v, tag) + err := rCustom(f, t, v, tag) if err == nil { return nil } + } else if isFakeable(t) { + value, err := callFake(f, v, reflect.Slice) + if err != nil { + return err + } + + v.Set(reflect.ValueOf(value)) + return nil } // Grab original size to use if needed for sub arrays @@ -210,7 +228,7 @@ func rSlice(ra *rand.Rand, t reflect.Type, v reflect.Value, tag string, size int // use that instead of the requested size elemLen := v.Len() if elemLen == 0 && size == -1 { - size = number(ra, 1, 10) + size = number(f.Rand, 1, 10) } else if elemLen != 0 && (size == -1 || elemLen < size) { size = elemLen } @@ -221,7 +239,7 @@ func rSlice(ra *rand.Rand, t reflect.Type, v reflect.Value, tag string, size int // Loop through the elements length and set based upon the index for i := 0; i < size; i++ { nv := reflect.New(elemT) - err := r(ra, elemT, nv.Elem(), tag, ogSize) + err := r(f, elemT, nv.Elem(), tag, ogSize) if err != nil { return err } @@ -237,7 +255,7 @@ func rSlice(ra *rand.Rand, t reflect.Type, v reflect.Value, tag string, size int return nil } -func rMap(ra *rand.Rand, t reflect.Type, v reflect.Value, tag string, size int) error { +func rMap(f *Faker, t reflect.Type, v reflect.Value, tag string, size int) error { // If you cant even set it dont even try if !v.CanSet() { return errors.New("cannot set slice") @@ -245,13 +263,23 @@ func rMap(ra *rand.Rand, t reflect.Type, v reflect.Value, tag string, size int) // Check if tag exists, if so run custom function if t.Name() != "" && tag != "" { - return rCustom(ra, t, v, tag) + return rCustom(f, t, v, tag) + } else if size > 0 { + //NOOP + } else if isFakeable(t) { + value, err := callFake(f, v, reflect.Map) + if err != nil { + return err + } + + v.Set(reflect.ValueOf(value)) + return nil } // Set a size newSize := size if newSize == -1 { - newSize = number(ra, 1, 10) + newSize = number(f.Rand, 1, 10) } // Create new map based upon map key value type @@ -261,14 +289,14 @@ func rMap(ra *rand.Rand, t reflect.Type, v reflect.Value, tag string, size int) for i := 0; i < newSize; i++ { // Create new key mapIndex := reflect.New(t.Key()) - err := r(ra, t.Key(), mapIndex.Elem(), "", -1) + err := r(f, t.Key(), mapIndex.Elem(), "", -1) if err != nil { return err } // Create new value mapValue := reflect.New(t.Elem()) - err = r(ra, t.Elem(), mapValue.Elem(), "", -1) + err = r(f, t.Elem(), mapValue.Elem(), "", -1) if err != nil { return err } @@ -286,116 +314,189 @@ func rMap(ra *rand.Rand, t reflect.Type, v reflect.Value, tag string, size int) return nil } -func rString(ra *rand.Rand, v reflect.Value, tag string) error { +func rString(f *Faker, t reflect.Type, v reflect.Value, tag string) error { if tag != "" { - v.SetString(generate(ra, tag)) + v.SetString(generate(f.Rand, tag)) + } else if isFakeable(t) { + value, err := callFake(f, v, reflect.String) + if err != nil { + return err + } + + valueStr, ok := value.(string) + if !ok { + return errors.New("call to Fake method did not return a string") + } + v.SetString(valueStr) } else { - v.SetString(generate(ra, strings.Repeat("?", number(ra, 4, 10)))) + v.SetString(generate(f.Rand, strings.Repeat("?", number(f.Rand, 4, 10)))) } return nil } -func rInt(ra *rand.Rand, t reflect.Type, v reflect.Value, tag string) error { +func rInt(f *Faker, t reflect.Type, v reflect.Value, tag string) error { if tag != "" { - i, err := strconv.ParseInt(generate(ra, tag), 10, 64) + i, err := strconv.ParseInt(generate(f.Rand, tag), 10, 64) if err != nil { return err } v.SetInt(i) - return nil - } + } else if isFakeable(t) { + value, err := callFake(f, v, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64) + if err != nil { + return err + } - // If no tag or error converting to int, set with random value - switch t.Kind() { - case reflect.Int: - v.SetInt(int64Func(ra)) - case reflect.Int8: - v.SetInt(int64(int8Func(ra))) - case reflect.Int16: - v.SetInt(int64(int16Func(ra))) - case reflect.Int32: - v.SetInt(int64(int32Func(ra))) - case reflect.Int64: - v.SetInt(int64Func(ra)) + switch i := value.(type) { + case int: + v.SetInt(int64(i)) + case int8: + v.SetInt(int64(i)) + case int16: + v.SetInt(int64(i)) + case int32: + v.SetInt(int64(i)) + case int64: + v.SetInt(int64(i)) + default: + return errors.New("call to Fake method did not return an integer") + } + } else { + // If no tag or error converting to int, set with random value + switch t.Kind() { + case reflect.Int: + v.SetInt(int64Func(f.Rand)) + case reflect.Int8: + v.SetInt(int64(int8Func(f.Rand))) + case reflect.Int16: + v.SetInt(int64(int16Func(f.Rand))) + case reflect.Int32: + v.SetInt(int64(int32Func(f.Rand))) + case reflect.Int64: + v.SetInt(int64Func(f.Rand)) + } } return nil } -func rUint(ra *rand.Rand, t reflect.Type, v reflect.Value, tag string) error { +func rUint(f *Faker, t reflect.Type, v reflect.Value, tag string) error { if tag != "" { - u, err := strconv.ParseUint(generate(ra, tag), 10, 64) + u, err := strconv.ParseUint(generate(f.Rand, tag), 10, 64) if err != nil { return err } v.SetUint(u) - return nil - } + } else if isFakeable(t) { + value, err := callFake(f, v, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64) + if err != nil { + return err + } - // If no tag or error converting to uint, set with random value - switch t.Kind() { - case reflect.Uint: - v.SetUint(uint64Func(ra)) - case reflect.Uint8: - v.SetUint(uint64(uint8Func(ra))) - case reflect.Uint16: - v.SetUint(uint64(uint16Func(ra))) - case reflect.Uint32: - v.SetUint(uint64(uint32Func(ra))) - case reflect.Uint64: - v.SetUint(uint64Func(ra)) + switch i := value.(type) { + case uint: + v.SetUint(uint64(i)) + case uint8: + v.SetUint(uint64(i)) + case uint16: + v.SetUint(uint64(i)) + case uint32: + v.SetUint(uint64(i)) + case uint64: + v.SetUint(uint64(i)) + default: + return errors.New("call to Fake method did not return an unsigned integer") + } + } else { + // If no tag or error converting to uint, set with random value + switch t.Kind() { + case reflect.Uint: + v.SetUint(uint64Func(f.Rand)) + case reflect.Uint8: + v.SetUint(uint64(uint8Func(f.Rand))) + case reflect.Uint16: + v.SetUint(uint64(uint16Func(f.Rand))) + case reflect.Uint32: + v.SetUint(uint64(uint32Func(f.Rand))) + case reflect.Uint64: + v.SetUint(uint64Func(f.Rand)) + } } return nil } -func rFloat(ra *rand.Rand, t reflect.Type, v reflect.Value, tag string) error { +func rFloat(f *Faker, t reflect.Type, v reflect.Value, tag string) error { if tag != "" { - f, err := strconv.ParseFloat(generate(ra, tag), 64) + f, err := strconv.ParseFloat(generate(f.Rand, tag), 64) if err != nil { return err } v.SetFloat(f) - return nil - } + } else if isFakeable(t) { + value, err := callFake(f, v, reflect.Float32, reflect.Float64) + if err != nil { + return err + } - // If no tag or error converting to float, set with random value - switch t.Kind() { - case reflect.Float64: - v.SetFloat(float64Func(ra)) - case reflect.Float32: - v.SetFloat(float64(float32Func(ra))) + switch i := value.(type) { + case float32: + v.SetFloat(float64(i)) + case float64: + v.SetFloat(float64(i)) + default: + return errors.New("call to Fake method did not return a float") + } + } else { + // If no tag or error converting to float, set with random value + switch t.Kind() { + case reflect.Float64: + v.SetFloat(float64Func(f.Rand)) + case reflect.Float32: + v.SetFloat(float64(float32Func(f.Rand))) + } } return nil } -func rBool(ra *rand.Rand, v reflect.Value, tag string) error { +func rBool(f *Faker, t reflect.Type, v reflect.Value, tag string) error { if tag != "" { - b, err := strconv.ParseBool(generate(ra, tag)) + b, err := strconv.ParseBool(generate(f.Rand, tag)) if err != nil { return err } v.SetBool(b) - return nil - } + } else if isFakeable(t) { + value, err := callFake(f, v, reflect.Bool) + if err != nil { + return err + } - // If no tag or error converting to boolean, set with random value - v.SetBool(boolFunc(ra)) + switch i := value.(type) { + case bool: + v.SetBool(bool(i)) + default: + return errors.New("call to Fake method did not return a boolean") + } + } else { + // If no tag or error converting to boolean, set with random value + v.SetBool(boolFunc(f.Rand)) + } return nil } // rTime will set a time.Time field the best it can from either the default date tag or from the generate tag -func rTime(ra *rand.Rand, t reflect.StructField, v reflect.Value, tag string) error { +func rTime(f *Faker, t reflect.StructField, v reflect.Value, tag string) error { if tag != "" { // Generate time - timeOutput := generate(ra, tag) + timeOutput := generate(f.Rand, tag) // Check to see if timeOutput has monotonic clock reading // if so, remove it. This is because time.Parse() does not @@ -435,6 +536,6 @@ func rTime(ra *rand.Rand, t reflect.StructField, v reflect.Value, tag string) er return nil } - v.Set(reflect.ValueOf(date(ra))) + v.Set(reflect.ValueOf(date(f.Rand))) return nil }