I have a db query that returns 2 result sets and I would like to unit test the go function that performs this query.
While I can add and test rows like this:
myMockRows:=sqlmock.NewRows([]string{"col1","col2"}).AddRow("col1val1", "col2val2")
mock.ExpectQuery("my_stored_procedure").WithArgs(arg1, arg2).WillReturnRows(myMockRows)
I am not sure how to proceed with creating multiple result sets in my rows object. How do I do that?
I had tried out, #Nikhil Vandanapu's answer and I wasn't getting the desired output. For some reason it took only myMockRows and myMockRows2 was ignored. I did some reading and we can do the following to get it to return multiple rows.
myMockRows:=sqlmock.NewRows([]string{"col1","col2"})
.AddRow("col1val1", "col2val2")
.AddRow("col1val1", "col2val2")
mock.ExpectQuery("my_stored_procedure").WillReturnRows(myMockRows)
According to godoc. Value slice return the same instance to perform subsequent actions.
Adding this blog post if you want an easier read about the topic
Do something like this:
myMockRows:=sqlmock.NewRows([]string{"col1","col2"}).AddRow("col1val1", "col2val2")
myMockRows2:=sqlmock.NewRows([]string{"col3","col4"}).AddRow("col3val1", "col4val2")
mock.ExpectQuery("my_stored_procedure").WithArgs(arg1, arg2).WillReturnRows(myMockRows, myMockRows2)
Since WillReturnRows accepts multiple row objects and forms a slice, use it to construct the next Result Set.
You have to get your test rows inside a struct and then execute loop over the struct like below code.
type args struct {
val string
}
tests := []struct {
name string
s *<YourDBstruct>
args args
wantErr bool
}{
{
name: "Test with correct value1",
s: &YourDBstruct{db},
args: args{"Val1"}
wantErr: true,
},
{
name: "Test with correct value2",
s: &YourDBstruct{db},
args: args{"Val2"}
wantErr: true,
},
{
name: "Test with correct valueN",
s: &YourDBstruct{db},
args: args{"ValN"}
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mock.ExpectExec("my_stored_procedure")
if err := tt.s.YourStoreFuncName(); (err != nil) != tt.wantErr {
t.Errorf("YourStoreFuncName() error = %v, wantErr %v", err, tt.wantErr)
}
})
Related
I have a Custom Resource which gets stuck in a catch22-like situation whenever a ROLLBACK occurs.
The code below is a simplified example of what my code is doing. In case of a create request, it creates a table, for delete it deletes, and in case of an update it compares the old properties with the new, and returns an error when one of the columns has a new value (column updates are not supported yet).
A problematic ROLLBACK_FAILED occurs when
[SOLVED] whenever a create request type fails (due to sql syntax error for example). In this case it will trigger a delete request for the ROLLBACK phase, but this request will fail because the table does not yet exist.
whenever a update request type fails due to a updated column value. In this case it will trigger a new update request for the ROLLBACK phase in which the event.ResourceProperties and event.OldResourceProperties are switched, which will still cause an error.
package main
import (
"context"
"encoding/json"
"fmt"
"strings"
"github.com/aws/aws-lambda-go/cfn"
"github.com/aws/aws-lambda-go/lambda"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/rdsdataservice"
"github.com/google/uuid"
)
func main() {
lambda.Start(cfn.LambdaWrap(handler))
}
type properties struct {
TableName string
Columns []struct {
Name string
Value string
}
}
func handler(ctx context.Context, event cfn.Event) (physicalResourceID string, data map[string]interface{}, err error) {
prid := event.PhysicalResourceID
if event.RequestType == cfn.RequestCreate {
prid = strings.ReplaceAll(uuid.New().String(), "-", "")
}
var props properties
b, _ := json.Marshal(event.ResourceProperties)
json.Unmarshal(b, &props)
rds := rdsdataservice.New(nil)
if event.RequestType == cfn.RequestCreate {
rds.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
Sql: aws.String(fmt.Sprintf("CREATE TABLE %s", props.TableName)),
})
}
if event.RequestType == cfn.RequestDelete {
rds.ExecuteStatement(&rdsdataservice.ExecuteStatementInput{
Sql: aws.String(fmt.Sprintf("CREATE TABLE %s", props.TableName)),
})
}
if event.RequestType == cfn.RequestUpdate {
var oldProps properties
b, _ := json.Marshal(event.OldResourceProperties)
json.Unmarshal(b, &oldProps)
columns := map[string]string{}
for _, column := range props.Columns {
columns[column.Name] = column.Value
}
for _, column := range oldProps.Columns {
if val, ok := columns[column.Name]; ok {
if val != column.Value {
return "", nil, fmt.Errorf("cannot change column type")
}
}
}
// Do some extra stuff here for adding/removing columns
}
return prid, nil, nil
}
I have thought of 2 possible solutions. One of them I could implement, with potential issues. But it seems to me there should be a better way, as I can't be the only one with this problem. Or I am doing something very wrong..
disable rollback for this specific resource only, in some cases (sometimes I still want a rollback)
have access to the last status, so that I can check what to do; in case of delete with last status CREATE_FAILED, don't do anything. In case of an update with last status UPDATE_FAILED, don't do anything.
The second option I could implement by using the code below. But as the number of events grow, this could become very problematic.
events, err := cloud.DescribeStackEvents(&cloudformation.DescribeStackEventsInput{
StackName: &event.StackID,
})
For anyone who runs into the same problem; I have solved this issue by looking at the stack events, and find the last resource status that is being updated. As the current status is always in the list, I ignore IN_PROGRESS values. If the first value after status IN_PROGRESS is FAILED, it means this resource could not be updated, and thus a different ROLLBACK strategie can be applied.
The corresponding function, in GO
func isFailedEvent(sess *session.Session, event cfn.Event) (bool, error) {
cloud := cloudformation.New(sess)
var isFailedResource bool
if err := cloud.DescribeStackEventsPages(&cloudformation.DescribeStackEventsInput{
StackName: aws.String(event.StackID),
}, func(out *cloudformation.DescribeStackEventsOutput, lastPage bool) bool {
for _, e := range out.StackEvents {
if *e.LogicalResourceId == event.LogicalResourceID {
if strings.HasSuffix(*e.ResourceStatus, "IN_PROGRESS") {
continue
}
if strings.HasSuffix(*e.ResourceStatus, "FAILED") && !strings.Contains(*e.ResourceStatusReason, "cancelled") {
isFailedResource = true
}
return false
}
}
return true
}); err != nil {
return false, fmt.Errorf("describe stack events: %s", err)
}
return isFailedResource, nil
}
This question already has an answer here:
Table Testing Go Generics
(1 answer)
Closed 4 months ago.
We were brainstorming possible ways of testing generic functions using table driven tests.
It seems pretty complicated at the first glance.
What we wanted to achieve is to have a field in the test table struct that can be of whatever type that is accepted by the generic function. It seems however, that you can't use any interface for that.
We came up with the following solution:
Example function:
func PtrTo[T any](t T) *T {
return &t
}
Example test:
func TestPtrTo(t *testing.T) {
string1 := "abcd"
trueVar := true
testCases := []struct {
testDescription string
input interface{}
expectedOutput interface{}
}{
{
testDescription: "string",
input: string1,
expectedOutput: &string1,
},
{
testDescription: "bool",
input: trueVar,
expectedOutput: &trueVar,
},
}
for _, testCase := range testCases {
t.Run(testCase.testDescription, func(t *testing.T) {
switch concreteTypeInput := testCase.input.(type) {
case string:
output := PtrTo(concreteTypeInput)
assert.Equal(t, testCase.expectedOutput, output)
case bool:
output := PtrTo(concreteTypeInput)
assert.Equal(t, testCase.expectedOutput, output)
default:
t.Error("Unexpected type. Please add the type to the switch case")
}
})
}
}
It doesn't really feel optimal, though.
What do you think of this solution?
Do you see any other alternatives?
A templated helper function could give you the semantics I think you're looking for:
package main
import (
"testing"
"github.com/google/go-cmp/cmp"
)
type testCase[T any] struct {
desc string
in T
want *T
}
func ptrToTest[T any](t *testing.T, tc testCase[T]) {
t.Helper()
t.Run(tc.desc, func(t *testing.T) {
got := PtrTo(tc.in)
if diff := cmp.Diff(tc.want, got); diff != "" {
t.Fatalf("got %v, want %v", got, tc.want)
}
})
}
func TestPtrTo(t *testing.T) {
string1 := "abcd"
ptrToTest(t, testCase[string]{
desc: "string",
in: string1,
want: &string1,
})
trueVar := true
ptrToTest(t, testCase[bool]{
desc: "bool",
in: trueVar,
want: &trueVar,
})
}
That being said, I agree with #blackgreen that the PtrTo function is too trivial for a test like this to be meaningful. Hopefully this is useful for more complicated logic!
I use Testify to create a unit test for my golang app. I need to create a unit test for this function where it calls a variadic function (function with trailing arguments). I encountered an error when I test it. I'm actually not sure if the error is because of the trailing argument itself or not, but I feel like there's something wrong with the mock.
// svc/callThisFunction.go
// data type of args is []sqkit.SelectOption
func CallThisFunction(ctx context.Context, args ...sqkit.SelectFunctiom) (result string, err error) {
return result, nil
}
// svc/functionToTest.go
// This is the function that I wanna test
func FunctionToTest(ctx context.Context, id int64) (result string, err error) {
args := []sqkit.SelectOption{
sqkit.Where{
fmt.Sprintf("id = %d", id),
},
}
newResult, err := callThisFunctionService.CallThisFunction(ctx, args)
if err != nil {
return newResult, err
}
return newResult, nil
}
// svc/functionToTest_test.go
func Test_FunctionToTest(t *testing.T) {
testCase := []struct {
name string
id int64
onCallThisFunctionMock func(callThisFunctionSvc *mocks.CallThisFunctionSvc)
expectedResult string
wantError bool
expectedError error
}{
{
name: "Success",
id: 1,
onCallThisFunctionMock: func(callThisFunctionSvc *mocks.CallThisFunctionSvc) {
// NOTE: I've created 2 different versions (used separately, not at the same), using mock.Anything() and using actual arguments
// Both of these give the same errors
// Using actual arguments
args := []sqkit.SelectOption{
sqkit.Where{
fmt.Sprintf("id = %d", 1},
},
}
callThisFunctionSvc.On("CallThisFunction", context.Background(), args).Return("Success", nil)
// Using mock.Anything
callThisFunctionSvc.On("CallThisFunction", context.Background(), mock.Anything).Return("Success", nil)
}
}
}
for _, tc := range testCases {
var callThisFunctionSvc = new(mocks.CallThisFunctionSvc)
tc.onCallThisFunctionMock(callThisFunctionSvc)
svc := &svc.FunctionToTest{
CallThisFunction: callThisFunctionSvc,
}
actualResult, actualError := svc.FunctionToTest(context.Background(), tc.id)
if tc.wantEror {
require.Error(t, actualError, tc.expectedError)
} else {
require.NoError(t, actualError)
}
require.Equal(t, tc.expectedResult, actualResult)
}
}
This is the error it gives
=== RUN Test_GenerateDocument
--- FAIL: Test_GenerateDocument (0.00s)
panic:
assert: mock: I don't know what to return because the method call was unexpected.
Either do Mock.On("CallThisFunction").Return(...) first, or remove the GetTemplates() call.
This method was unexpected:
CallThisFunction(*context.emptyCtx,sqkit.Where)
0: (*context.emptyCtx)(0xc0000a4010)
1: sqkit.Where{"id = 1"}
Usually, when I encountered an error like this, it's because I haven't defined the return values of the function calls inside the function I wanna test. But this time I've created it, but it somehow can't read the return. Any idea why?
The error indicates you called CallThisFuncion with params (context.Context, sqkit.Where), but your example is using and setting the expectation for (context.Context, []sqkit.Option). The example with mock.Anything should work, but I believe it's failing because of the context. You'll need to set the expectation with the same context you're passing down. If FunctionToTest is going to be altering the context, I believe you'll need to use mock.Anything instead.
func Test_FunctionToTest(t *testing.T) {
testCase := []struct {
name string
id int64
onCallThisFunctionMock func(context.Context, *mocks.CallThisFunctionSvc)
expectedResult string
wantError bool
expectedError error
}{
{
name: "Success",
id: 1,
onCallThisFunctionMock: func(ctx context.Context, callThisFunctionSvc *mocks.CallThisFunctionSvc) {
args := []sqkit.SelectOption{
sqkit.Where{
fmt.Sprintf("id = %d", 1},
},
}
callThisFunctionSvc.On("CallThisFunction", ctx, args).Return("Success", nil)
}
}
}
for _, tc := range testCases {
var callThisFunctionSvc = new(mocks.CallThisFunctionSvc)
var ctx = context.Background()
tc.onCallThisFunctionMock(ctx, callThisFunctionSvc)
svc := &svc.FunctionToTest{
CallThisFunction: callThisFunctionSvc,
}
actualResult, actualError := svc.FunctionToTest(ctx, tc.id)
if tc.wantEror {
require.Error(t, actualError, tc.expectedError)
} else {
require.NoError(t, actualError)
}
require.Equal(t, tc.expectedResult, actualResult)
}
}
If you want to ensure a context.Context was passed as the first parameter but don't care what context, you could use AnythingOfType.
callThisFunctionSvc.On("CallThisFunction", mock.AnythingOfType("context.Context"), args).Return("Success", nil)
I'm trying to create a unit test on Go using testify. The function I'm trying to test is quite simple, it'll start an SQL transaction, and then get values from DB through the repo layer, and then calculate those values.
But whenever I'm trying to run the unit test, I always encountered this error.
# go test -v usecase/calculate_test.go usecase/calculate.go usecase/other_related_usecase.go
=== RUN Test_Calculate
[2022-07-15 08:42:30] INFO: Start Calculation
--- FAIL: Test_Calculate (0.00s)
panic:
assert: mock: I don't know what to return because the method call was unexpected.
Either do Mock.On("BeginTxx").Return(...) first, or remove the BeginTxx() call.
This method was unexpected:
BeginTxx()
at: [sql_repository.go:1 calculate.go:4 calculate_test.go:2] [recovered]
panic:
assert: mock: I don't know what to return because the method call was unexpected.
Either do Mock.On("BeginTxx").Return(...) first, or remove the BeginTxx() call.
This method was unexpected:
BeginTxx()
at: [sql_repository.go:1 calculate.go:4 calculate_test.go:2]
From what I see, I think it's becase it can't find the expected method call on the unit test. But, the problem is that I already defined the method call here inside the onSQLMock field.
func Test_Calculate(t *testing.T) {
testCases := []struct {
name string
id int64
onSQLMock func(sqlMock *mocks.SQLRepository)
onOtherFuncMock func(otherFuncMock *mocks.OtherFuncMock)
wantError bool
expectedError string
}{
{
name: "Successfully Calculated",
onSQLMock: func(sqlMock *mocks.SQLRepository) {
var tx *sqlx.Tx
sqlRepo.On("BeginTxx").Return(tx, nil)
},
onOtherFuncMock: func(otherFuncMock *Mocks.OtherFuncMock) {
// do something here
},
wantError: false,
},{
name: "Failed to calculate",
onSQLMock: func(sqlMock *mocks.SQLRepository) {
var tx *sqlx.Tx
sqlRepo.On("BeginTxx").Return(tx, errors.New("Failed to begin tx")
},
onOtherFuncMock: func(otherFuncMock *mocks.OtherFuncMock) {
// do something here
},
wantError: true,
expectedError: "Failed to begin tx",
},
}
}
for _, testCase := range testCases {
uc := &usecase{
sqlRepo: new(mocks.SQLRepository),
otherFuncMock: new(mocks.OtherFuncMock),
}
actualErrx := uc.Calculate(context.Background(), testCase.id)
if testCase.wantError {
require.EqualError(t, actualErrx, testCase.expectedError)
}
}
}
func (_m *SQLRepository) BeginTxx() (*sqlx.Tx, error) {
ret := _m.Called()
var r0 *sqlx.Tx
if rf, ok := ret.Get(0).(func() *sqlx.Tx); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*sqlx.Tx)
}
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
Anyone can figure what was wrong on my code? I can't figure out because it looks like I already did what the error asked me to do.
You don't actually use your onSQLMock anywhere.
Use it before you pass the mock repository to the usecase instance
mockRepo := new(mocks.SQLRepository)
testCase.onSQLMock(mockRepo)
Also use the parameter of the function:
onSQLMock: func(sqlMock *mocks.SQLRepository) {
var tx *sqlx.Tx
sqlMock.On("BeginTxx").Return(tx, nil)
},
I'm validating my config file using Go's validator package
One of the fields(createTime) from the config file I'm reading as a string, and want to make sure that it is a valid time duration:
type Config struct{
...
CreateTime string `yaml:"createTime" validate:"duration,required"`
...
}
So, I have written a custom validation function as follows:
import (
"time"
"github.com/go-playground/validator/v10"
)
// isValidDuration is a custom validation function, and it will check if the given string is a valid time duration
func isValidDuration(fl validator.FieldLevel) bool {
if _, err := time.ParseDuration(fl.Field().String()); err != nil {
return false
}
return true
}
Which I'm using for validation as follows:
func (configObject *Config) Validate() error {
// Validate configurations
validate := validator.New()
if err := validate.RegisterValidation("duration", isValidDuration); err != nil {
return err
}
return validate.Struct(configObject)
}
The custom validator is working fine and I want to write a unit test for isValidDuration function. Following is the unit test boilerplate generated by IDE:
import (
"testing"
"github.com/go-playground/validator/v10"
)
func Test_isValidDuration(t *testing.T) {
type args struct {
fl validator.FieldLevel
}
var tests = []struct {
name string
args args
want bool
}{
// TODO: Add test cases.
{name: "positive", args: ???????, want: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := isValidDuration(tt.args.fl); got != tt.want {
t.Errorf("isValidDuration() = %v, want %v", got, tt.want)
}
})
}
}
I'm new to Go and not sure what to pass in the args field of the testcase above. How do I create a struct containing one validator.FieldLevel field?
Ideally, I would like to pass something like "10m" as args and since it is a valid time duration, would expect the output of isValidDuration as true since "10m" is a valid duration.
I'm trying this: {name: "positive", args: struct{ fl validator.FieldLevel }{fl: "10m"}, want: true} but getting this ereor: '"10m"' (type string) cannot be represented by the type validator.FieldLevel
How do I create a validator.FieldLevel variable with value equivalent to "10m"? Could someone please help me out?
problem1
A language semantic problem, fl is not the type String.
{
name: "positive",
args: args{
fl: validator.FieldLevel{
// ....
},
},
want: true,
},
problem2
How to use validator.FieldLevel.
In code base, we can see FieldLevel is a interface, and you need create struct validate, which is not exported and not supposed to be used by user.
type FieldLevel interface
// ...
var _ FieldLevel = new(validate)
answer
So, you'd best to write your UT like UT of validator package. Don't use the code from your IDE!
// have a look at this UT
// github.com/go-playground/validator/v10#v10.10.0/validator_test.go
func TestKeysCustomValidation(t *testing.T) {