diff --git a/README.md b/README.md index 63b44d0..afcdf41 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,40 @@ # lazy -a helper tool for using sqlmock with sqlx tests \ No newline at end of file +Lazy is a helper tool when working with SQLx and sqlmock that generates mock data based on the result struct. + +## GenerateRandomResults +This function expects the SQL query, the example response object, and an optional primary key to set. + +Query The SQL query argument is taken and rebound using sqlx.AT bindvar type, this allows the returned query to be used directly in a sqlmock [ExpectQuery](https://pkg.go.dev/github.com/data-dog/go-sqlmock#Sqlmock.ExpectQuery) check. + +Example Object The example object argument must be a struct and requires "db" tags. The db tags are then parsed and used to calculate the psuedo-random rows and values + +Primary Key The optional primary key argument is used to hardcode a primary key field in the returned mocks, the primary key field in the example struct must have a test tag with the value "key" +```go +type Mock struct { + Field1 string `db:"field1" test:"key"` +} +``` + +## Example +```go +func Test() { + testArg := 123 + query := `SELECT ...` + type results struct { + Field1 string `db:"field1" test:"key"` + Field2 int `db:"field2"` + } + + rando, err := GenerateRandomResults(query, results{}, testArg) + if err != nil { + panic(err) + } + + //the rando object will now have psuedo random values in all fields except for the field containing the test tag set to "key" that field will be hardcoded with the testArg to allow for unit tests to ensure the requested ID flows through + rows := sqlmock.NewRows(rando.Columns).AddRow(rando.Rows...) + mock.ExpectQuery(rando.Query).WithArgs(testArg).WillReturnRows(rows) + + ... +} +``` \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..31b9da9 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module code.jakeyoungdev.com/go/lazy + +go 1.20 + +require github.com/jmoiron/sqlx v1.4.0 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..f4ce337 --- /dev/null +++ b/go.sum @@ -0,0 +1,10 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= +github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= diff --git a/lazy.go b/lazy.go new file mode 100644 index 0000000..5afefe8 --- /dev/null +++ b/lazy.go @@ -0,0 +1,97 @@ +package lazy + +import ( + "database/sql/driver" + "errors" + "fmt" + "math/rand/v2" + "reflect" + "regexp" + + "github.com/jmoiron/sqlx" +) + +type MockResults struct { + Query string + Columns []string + Rows []driver.Value +} + +func GenerateRandomResults(query string, exampleObj any, keyVal any) (*MockResults, error) { + if exampleObj == nil { + return nil, errors.New("exampleObj cannot be nil") + } + + retType := reflect.TypeOf(exampleObj) + maxFieldCount := retType.NumField() + columns := make([]string, 0, maxFieldCount) + rows := make([]driver.Value, 0, maxFieldCount) + + for x := 0; x < maxFieldCount; x++ { + field := retType.Field(x) + dbTag := field.Tag.Get("db") + if dbTag == "" { + continue + } + + columns = append(columns, dbTag) + + if field.Tag.Get("test") == "key" { + rows = append(rows, keyVal) + continue + } + + nv := kindToRandom(field) + if nv == nil { + return nil, fmt.Errorf("could not match type: %s", retType.Name()) + } + + rows = append(rows, nv) + } + + return &MockResults{ + Query: sqlx.Rebind(sqlx.AT, regexp.QuoteMeta(query)), + Columns: columns, + Rows: rows, + }, nil +} + +func kindToRandom(field reflect.StructField) any { + kind := field.Type.Kind() + switch kind { + case reflect.Int: + return rand.Int() + case reflect.Int32: + return rand.Int32() + case reflect.Int64: + return rand.Int64() + case reflect.Float32: + return rand.Float32() + case reflect.Float64: + return rand.Float64() + case reflect.String: + return fmt.Sprintf("%d", rand.Int()) + case reflect.Bool: + return rand.Int()%2 == 0 + case reflect.Slice: + underlying := field.Type.Elem().Kind() + switch underlying { + case reflect.Int: + return []int{rand.Int()} + case reflect.Int32: + return []int32{rand.Int32()} + case reflect.Int64: + return []int64{rand.Int64()} + case reflect.Float32: + return []float32{rand.Float32()} + case reflect.Float64: + return []float64{rand.Float64()} + case reflect.String: + return []string{fmt.Sprintf("%d", rand.Int())} + case reflect.Bool: + return []bool{rand.Int()%2 == 0} + } + } + + return nil +}