Reorganize files so the test helpers are grouped in the same pkg

pull/2/head
Vinícius Garcia 2021-06-11 12:25:24 -03:00
parent 5b9b0dd00d
commit 20f49eb22b
7 changed files with 172 additions and 164 deletions

View File

@ -469,9 +469,10 @@ that we actually care about, so it's better not to use composite structs.
This library has a few helper functions for helping your tests: This library has a few helper functions for helping your tests:
- `ksql.FillStructWith(struct interface{}, dbRow map[string]interface{}) error` - `structs.FillStructWith(struct interface{}, dbRow map[string]interface{}) error`
- `ksql.FillSliceWith(structSlice interface{}, dbRows []map[string]interface{}) error` - `structs.FillSliceWith(structSlice interface{}, dbRows []map[string]interface{}) error`
- `ksql.StructToMap(struct interface{}) (map[string]interface{}, error)` - `structs.StructToMap(struct interface{}) (map[string]interface{}, error)`
- `structs.CallFunctionWithRows(fn interface{}, rows []map[string]interface{}) (map[string]interface{}, error)`
If you want to see examples (we have examples for all the public functions) just If you want to see examples (we have examples for all the public functions) just
read the example tests available on our [example service](./examples/example_service) read the example tests available on our [example service](./examples/example_service)

View File

@ -198,7 +198,7 @@ func TestStreamAllUsers(t *testing.T) {
mockDB.EXPECT().QueryChunks(gomock.Any(), gomock.Any()). mockDB.EXPECT().QueryChunks(gomock.Any(), gomock.Any()).
DoAndReturn(func(ctx context.Context, parser ksql.ChunkParser) error { DoAndReturn(func(ctx context.Context, parser ksql.ChunkParser) error {
// Chunk 1: // Chunk 1:
err := ksql.CallFunctionWithRows(parser.ForEachChunk, []map[string]interface{}{ err := structs.CallFunctionWithRows(parser.ForEachChunk, []map[string]interface{}{
{ {
"id": 1, "id": 1,
"name": "fake name", "name": "fake name",
@ -215,7 +215,7 @@ func TestStreamAllUsers(t *testing.T) {
} }
// Chunk 2: // Chunk 2:
err = ksql.CallFunctionWithRows(parser.ForEachChunk, []map[string]interface{}{ err = structs.CallFunctionWithRows(parser.ForEachChunk, []map[string]interface{}{
{ {
"id": 3, "id": 3,
"name": "yet another fake name", "name": "yet another fake name",

34
ksql.go
View File

@ -244,7 +244,7 @@ func (c DB) QueryChunks(
parser ChunkParser, parser ChunkParser,
) error { ) error {
fnValue := reflect.ValueOf(parser.ForEachChunk) fnValue := reflect.ValueOf(parser.ForEachChunk)
chunkType, err := parseInputFunc(parser.ForEachChunk) chunkType, err := structs.ParseInputFunc(parser.ForEachChunk)
if err != nil { if err != nil {
return err return err
} }
@ -759,38 +759,6 @@ func (c DB) Transaction(ctx context.Context, fn func(SQLProvider) error) error {
} }
} }
var errType = reflect.TypeOf(new(error)).Elem()
func parseInputFunc(fn interface{}) (reflect.Type, error) {
if fn == nil {
return nil, fmt.Errorf("the ForEachChunk attribute is required and cannot be nil")
}
t := reflect.TypeOf(fn)
if t.Kind() != reflect.Func {
return nil, fmt.Errorf("the ForEachChunk callback must be a function")
}
if t.NumIn() != 1 {
return nil, fmt.Errorf("the ForEachChunk callback must have 1 argument")
}
if t.NumOut() != 1 {
return nil, fmt.Errorf("the ForEachChunk callback must have a single return value")
}
if t.Out(0) != errType {
return nil, fmt.Errorf("the return value of the ForEachChunk callback must be of type error")
}
argsType := t.In(0)
if argsType.Kind() != reflect.Slice {
return nil, fmt.Errorf("the argument of the ForEachChunk callback must a slice of structs")
}
return argsType, nil
}
type nopScanner struct{} type nopScanner struct{}
var nopScannerValue = reflect.ValueOf(&nopScanner{}).Interface() var nopScannerValue = reflect.ValueOf(&nopScanner{}).Interface()

40
structs/func_parser.go Normal file
View File

@ -0,0 +1,40 @@
package structs
import (
"fmt"
"reflect"
)
var errType = reflect.TypeOf(new(error)).Elem()
// ParseInputFunc is used exclusively for parsing
// the ForEachChunk function used on the QueryChunks method.
func ParseInputFunc(fn interface{}) (reflect.Type, error) {
if fn == nil {
return nil, fmt.Errorf("the ForEachChunk attribute is required and cannot be nil")
}
t := reflect.TypeOf(fn)
if t.Kind() != reflect.Func {
return nil, fmt.Errorf("the ForEachChunk callback must be a function")
}
if t.NumIn() != 1 {
return nil, fmt.Errorf("the ForEachChunk callback must have 1 argument")
}
if t.NumOut() != 1 {
return nil, fmt.Errorf("the ForEachChunk callback must have a single return value")
}
if t.Out(0) != errType {
return nil, fmt.Errorf("the return value of the ForEachChunk callback must be of type error")
}
argsType := t.In(0)
if argsType.Kind() != reflect.Slice {
return nil, fmt.Errorf("the argument of the ForEachChunk callback must a slice of structs")
}
return argsType, nil
}

View File

@ -4,8 +4,6 @@ import (
"fmt" "fmt"
"reflect" "reflect"
"strings" "strings"
"github.com/pkg/errors"
) )
// StructInfo stores metainformation of the struct // StructInfo stores metainformation of the struct
@ -120,56 +118,6 @@ func StructToMap(obj interface{}) (map[string]interface{}, error) {
return m, nil return m, nil
} }
// FillStructWith is meant to be used on unit tests to mock
// the response from the database.
//
// The first argument is any struct you are passing to a ksql func,
// and the second is a map representing a database row you want
// to use to update this struct.
func FillStructWith(record interface{}, dbRow map[string]interface{}) error {
v := reflect.ValueOf(record)
t := v.Type()
if t.Kind() != reflect.Ptr {
return fmt.Errorf(
"FillStructWith: expected input to be a pointer to struct but got %T",
record,
)
}
t = t.Elem()
v = v.Elem()
if t.Kind() != reflect.Struct {
return fmt.Errorf(
"FillStructWith: expected input kind to be a struct but got %T",
record,
)
}
info := getCachedTagInfo(tagInfoCache, t)
for colName, rawSrc := range dbRow {
fieldInfo := info.ByName(colName)
if !fieldInfo.Valid {
// Ignore columns not tagged with `ksql:"..."`
continue
}
src := NewPtrConverter(rawSrc)
dest := v.Field(fieldInfo.Index)
destType := t.Field(fieldInfo.Index).Type
destValue, err := src.Convert(destType)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("FillStructWith: error on field `%s`", colName))
}
dest.Set(destValue)
}
return nil
}
// PtrConverter was created to make it easier // PtrConverter was created to make it easier
// to handle conversion between ptr and non ptr types, e.g.: // to handle conversion between ptr and non ptr types, e.g.:
// //
@ -251,49 +199,6 @@ func (p PtrConverter) Convert(destType reflect.Type) (reflect.Value, error) {
return destValue, nil return destValue, nil
} }
// FillSliceWith is meant to be used on unit tests to mock
// the response from the database.
//
// The first argument is any slice of structs you are passing to a ksql func,
// and the second is a slice of maps representing the database rows you want
// to use to update this struct.
func FillSliceWith(entities interface{}, dbRows []map[string]interface{}) error {
sliceRef := reflect.ValueOf(entities)
sliceType := sliceRef.Type()
if sliceType.Kind() != reflect.Ptr {
return fmt.Errorf(
"FillSliceWith: expected input to be a pointer to a slice of structs but got %v",
sliceType,
)
}
structType, isSliceOfPtrs, err := DecodeAsSliceOfStructs(sliceType.Elem())
if err != nil {
return errors.Wrap(err, "FillSliceWith")
}
slice := sliceRef.Elem()
for idx, row := range dbRows {
if slice.Len() <= idx {
var elemValue reflect.Value
elemValue = reflect.New(structType)
if !isSliceOfPtrs {
elemValue = elemValue.Elem()
}
slice = reflect.Append(slice, elemValue)
}
err := FillStructWith(slice.Index(idx).Addr().Interface(), row)
if err != nil {
return errors.Wrap(err, "FillSliceWith")
}
}
sliceRef.Elem().Set(slice)
return nil
}
// This function collects only the names // This function collects only the names
// that will be used from the input type. // that will be used from the input type.
// //

125
structs/testhelpers.go Normal file
View File

@ -0,0 +1,125 @@
package structs
import (
"fmt"
"reflect"
"github.com/pkg/errors"
)
// FillStructWith is meant to be used on unit tests to mock
// the response from the database.
//
// The first argument is any struct you are passing to a ksql func,
// and the second is a map representing a database row you want
// to use to update this struct.
func FillStructWith(record interface{}, dbRow map[string]interface{}) error {
v := reflect.ValueOf(record)
t := v.Type()
if t.Kind() != reflect.Ptr {
return fmt.Errorf(
"FillStructWith: expected input to be a pointer to struct but got %T",
record,
)
}
t = t.Elem()
v = v.Elem()
if t.Kind() != reflect.Struct {
return fmt.Errorf(
"FillStructWith: expected input kind to be a struct but got %T",
record,
)
}
info := GetTagInfo(t)
for colName, rawSrc := range dbRow {
fieldInfo := info.ByName(colName)
if !fieldInfo.Valid {
// Ignore columns not tagged with `ksql:"..."`
continue
}
src := NewPtrConverter(rawSrc)
dest := v.Field(fieldInfo.Index)
destType := t.Field(fieldInfo.Index).Type
destValue, err := src.Convert(destType)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("FillStructWith: error on field `%s`", colName))
}
dest.Set(destValue)
}
return nil
}
// FillSliceWith is meant to be used on unit tests to mock
// the response from the database.
//
// The first argument is any slice of structs you are passing to a ksql func,
// and the second is a slice of maps representing the database rows you want
// to use to update this struct.
func FillSliceWith(entities interface{}, dbRows []map[string]interface{}) error {
sliceRef := reflect.ValueOf(entities)
sliceType := sliceRef.Type()
if sliceType.Kind() != reflect.Ptr {
return fmt.Errorf(
"FillSliceWith: expected input to be a pointer to a slice of structs but got %v",
sliceType,
)
}
structType, isSliceOfPtrs, err := DecodeAsSliceOfStructs(sliceType.Elem())
if err != nil {
return errors.Wrap(err, "FillSliceWith")
}
slice := sliceRef.Elem()
for idx, row := range dbRows {
if slice.Len() <= idx {
var elemValue reflect.Value
elemValue = reflect.New(structType)
if !isSliceOfPtrs {
elemValue = elemValue.Elem()
}
slice = reflect.Append(slice, elemValue)
}
err := FillStructWith(slice.Index(idx).Addr().Interface(), row)
if err != nil {
return errors.Wrap(err, "FillSliceWith")
}
}
sliceRef.Elem().Set(slice)
return nil
}
// CallFunctionWithRows was created for helping test the QueryChunks method
func CallFunctionWithRows(fn interface{}, rows []map[string]interface{}) error {
fnValue := reflect.ValueOf(fn)
chunkType, err := ParseInputFunc(fn)
if err != nil {
return err
}
chunk := reflect.MakeSlice(chunkType, 0, len(rows))
// Create a pointer to a slice (required by FillSliceWith)
chunkPtr := reflect.New(chunkType)
chunkPtr.Elem().Set(chunk)
err = FillSliceWith(chunkPtr.Interface(), rows)
if err != nil {
return err
}
err, _ = fnValue.Call([]reflect.Value{chunkPtr.Elem()})[0].Interface().(error)
return err
}

View File

@ -1,31 +0,0 @@
package ksql
import (
"reflect"
"github.com/vingarcia/ksql/structs"
)
// CallFunctionWithRows was created for helping test the QueryChunks method
func CallFunctionWithRows(fn interface{}, rows []map[string]interface{}) error {
fnValue := reflect.ValueOf(fn)
chunkType, err := parseInputFunc(fn)
if err != nil {
return err
}
chunk := reflect.MakeSlice(chunkType, 0, len(rows))
// Create a pointer to a slice (required by FillSliceWith)
chunkPtr := reflect.New(chunkType)
chunkPtr.Elem().Set(chunk)
err = structs.FillSliceWith(chunkPtr.Interface(), rows)
if err != nil {
return err
}
err, _ = fnValue.Call([]reflect.Value{chunkPtr.Elem()})[0].Interface().(error)
return err
}