I am still grasping go-interfaces and I can mock the WaitUntilTableExists func. But unable to mock PutItemRequest.
Here's my main.go snippet
func MyPutItem(d mydata, client dynamodbiface.DynamoDBAPI) error {
input := &dynamodb.PutItemInput{
....
}
req := client.PutItemRequest(input)
result, err := req.Send()
log.Println(result)
return err
}
main_test.go snippet
type mockDynamoDBClient struct {
dynamodbiface.DynamoDBAPI
}
func (m *mockDynamoDBClient) PutItemRequest(input *dynamodb.PutItemInput) dynamodb.PutItemRequest {
// Most probably this is where I need your help
}
func TestStoreInDynamoDB(t *testing.T) {
var mockClient = new(mockDynamoDBClient)
d := mydata{}
result := DynampDBPutItem(d, mockClient)
t.Log(result)
}
Taking your example, you could do your assertions directly in the mock
type mockDynamoDBClient struct {
t *testing.T
expected *dynamodb.PutItemInput
response *dynamodb.PutItemOutput
dynamodbiface.DynamoDBAPI
}
func (m *mockDynamoDBClient) PutItemRequest(input *dynamodb.PutItemInput) dynamodb.PutItemOutput {
// some kind of equality check
if !reflect.DeepEqual(m.expected, input) {
t.Errorf(...// some error message)
}
return m.response
}
The main problems with this example are:
t *testing.T, expected *dynamodb.PutItemInput and response response *dynamodb.PutItemOutput all need to be inside the struct which feels messy.
Instead you could use an anonymous function to do this:
type mockDynamoDBClient struct {
f func(input *dynmaodb.PutItemInput) *dynamodb.PutItemOutput
dynamodbiface.DynamoDBAPI
}
func (m *mockDynamoDBClient) PutItemRequest(input *dynamodb.PutItemInput) dynamodb.PutItemOutput {
return m.f(input)
}
Now in the test code you can make slightly better use of the mock struct:
m := &mockDynamoDBClient{
f: func(input *dynamodb.PutItemInput) *dynamodb.PutItemOutput {
// assertions on input
// return mock responses
}
}
EDIT based on comment:
You should also consider making your MyPutItem function dependent on the smallest interface possible. If you only need access to the PutItemRequest method then you can create your own interface for that method and use that in MyPutItem
type MyDynamoPutter interface {
func (c *DynamoDB) PutItemRequest(input *PutItemInput) PutItemRequest
}
Then in MyPutItem you can use your own interface:
func MyPutItem(d mydata, client MyDynamoPutter) error {
input := &dynamodb.PutItemInput{
....
}
req := client.PutItemRequest(input)
result, err := req.Send()
log.Println(result)
return err
}
This reduces the surface area that you need to mock!
Faking the SDK like this works:
main_test.go
type fakeDynamoDBClient struct {
dynamodbiface.DynamoDBAPI
}
func (m *fakeDynamoDBClient) GetItemRequest(input *dynamodb.GetItemInput) dynamodb.GetItemRequest {
return dynamodb.GetItemRequest{
Request: &aws.Request{
Data: &dynamodb.GetItemOutput{
Item: map[string]dynamodb.AttributeValue{
"count": dynamodb.AttributeValue{
N: aws.String("10"),
},
},
},
},
}
}
func (m *fakeDynamoDBClient) PutItemRequest(input *dynamodb.PutItemInput) dynamodb.PutItemRequest {
return dynamodb.PutItemRequest{
Request: &aws.Request{
Data: &dynamodb.PutItemOutput{},
},
}
}
func TestUpdateCount(t *testing.T) {
err := UpdateCount(10, &fakeDynamoDBClient{})
if err != nil {
t.Error("Failed to update badge count on dynamodb", err)
}
}
main.go
func UpdateCount(count int, client dynamodbiface.DynamoDBAPI) error {
...
}
Related
This is the code I am testing
func listObjects(cli *client.Client, options clientOptions) ([]BlobObjects, error) {
objects, err := cli.ListBlobObjects(...)
}
In my test setup I do this
type MockClient struct {
MockListBlobObjects func() ([]BlobObjects, error)
}
func (m *MockClient) ListBlobObjects(....) ([]BlobObjects, error) {
// return some mock response
}
And this is my test case
func TestBlobObjects(t *testing.T) {
tests := map[string]struct {
client *MockClient
...
}{
"Test case 1": {
client: &MockClient{
MockListBlobObjects: ....,
},
....
},
....
for testName, test := range tests {
blobs, err := (test.client, clientOptions{})
// make assertions here
}
The problem is test.client. Compiler is telling me
cannot use test.client (variable of type *MockClient) as *client.Client value in argument to listObjects
My hope was I have a mock client and if I call the function under test, then the mock client passed will call the mocked listObjects. This is how I would do in Python.
What should I do in Golang?
You need to create a common interface, which will be implemented by both types
type Client interface {
ListBlobObjects func() ([]BlobObjects, error)
}
type RealClient struct {
}
func (c *RealClient) ListBlobObjects(....) ([]BlobObjects, error) {
// return some response
}
type MockClient struct {
// note: don't put method signature in function body
}
func (m *MockClient) ListBlobObjects(....) ([]BlobObjects, error) {
// return some mock response
}
and then
var client client.Client
client = &RealClient{}
// or
client = &MockClient{}
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 having the following problem when unit testing (working code here]
Assuming we have this:
var (
userDomain UserInterface
Tx TxInterface
)
type UserInterface interface {
Get() (*User, error)
}
type TxInterface interface {
Exec(fn func() error) error
}
type User struct {
ID int64
}
func (u *User) Get() (*User, error) {
return &User{
ID: 1,
}, nil
}
type WithTx struct{}
func (t *WithTx) Exec(fn func() error) error {
/* more logic here */
return fn()
}
Assuming we have the following 2 services:
func GetByID() (*User, error) {
user, err := userDomain.Get()
return user, err
}
func GetByIDWithTx() (*User, error) {
u := &User{}
/** Notice this implementation **/
if err := Tx.Exec(func() error {
user, _ := userDomain.Get()
u = user
return nil
}); err != nil {
log.Print("Tx err", err)
}
return u, nil
}
They are the same but once goes directly to the Get method, the other one runs into a callback fn. So far so go, both works like a charm. Again code is here
I'm trying to mock the userDomain and Tx for unit tests, so my mocks look like this:
var (
getUserFromMock func() (*User, error)
getFromMock func() error
)
type userMock struct{}
func (u *userMock) Get() (*User, error) {
return getUserFromMock()
}
type txMock struct{}
func (t *txMock) Exec(fn func() error) error {
return getFromMock()
}
When testing GetByID I have no problem whatsoever.
func TestGetByID(t *testing.T) {
userDomain = &userMock{}
Tx = &txMock{}
getUserFromMock = func() (*User, error) {
return &User{ID: 3}, nil
}
user, err := GetByID()
if user.ID != 3 {
t.Fatalf("error")
}
if err != nil {
t.Fatalf("error")
}
}
However, When I try to testing GetByIDWithTx, I cannot assert the mock values:
func TestGetByIDWithTx(t *testing.T) {
userDomain = &userMock{}
Tx = &txMock{}
getUserFromMock = func() (*User, error) {
return &User{ID: 4}, nil
}
/**** Mocking callback response ****/
getFromMock = func() error {
return nil
}
user, err := GetByIDWithTx()
if user.ID != 4 {
t.Fatalf("error") /** It fails here **/
}
if err != nil {
t.Fatalf("error")
}
}
I assume, at the moment I'm trying to assert the callback is still running in another routine but I'm just speculating, I don't have any idea what is going on here. I rather not use any library for a spy, I just want to understand how to solve this. Thoughts?
Go PlayGround HERE
You are never calling the fn given to the txmock. Change txMock.Exec to:
type txMock struct{}
func (t *txMock) Exec(fn func() error) error {
return fn()
// return getFromMock()
}
and remove getFromMock() function completely. It will then call getUserFromMock function to get the mocked user.
I'm writing unit tests, and I want to write a unit test that asserts that a public method on the struct (Foo.Start) properly handles error responses from an internal method on the struct (Foo.internal).
Essentially I want to get test coverage on the this section of my code:
if err != nil {
return err
}
Here's an example of code and associated test that doesn't work (but would work in say, Python)
# example.go
package example
import "fmt"
type FooAPI interface {
Start() error
internal(string) (string, error)
}
type Foo struct {
FooAPI
}
func (foo Foo) Start() (err error) {
data, err := foo.internal("bar")
if err != nil {
return err
}
fmt.Println(data)
return err
}
func (foo Foo) internal(input string) (output string, err error) {
return output, err
}
# example_test.go
package example
import (
"testing"
"github.com/pkg/errors"
)
type MockFoo struct {
FooAPI
}
func (foo MockFoo) internal(input string) (output string, err error) {
return output, errors.New("big error")
}
func TestStart(t *testing.T) {
tdata := []struct {
testCase string
expectedAnError bool
foo FooAPI
}{
{
testCase: "standard_case",
expectedAnError: false,
foo: Foo{},
},
{
testCase: "error_case",
expectedAnError: true,
foo: MockFoo{},
},
}
for _, test := range tdata {
t.Run(test.testCase, func(t *testing.T) {
// The function under test
test.foo.Start = Foo.Start // <= this doesn't work
err := test.foo.Start()
// Assertion 1
if test.expectedAnError == false && err != nil {
t.Error(err.Error())
}
// Assertion 2
if test.expectedAnError == true && err == nil {
t.Error("expected an error, but there was none!")
}
})
}
}
I'm less interested in correcting the specific example, more-so my goal is to get test coverage on Foo.Start's error handling. I feel like there's some trick with interfaces or pointers that will get me across the finish line here?
I figured out one solution, inspired by https://stackoverflow.com/a/48206430/3055558
Using a internal{{ your struct }} struct and associated interface, and mocking that.
# example.go
package example
import "fmt"
type internalFooAPI interface {
internalBehavior(string) (string, error)
}
type Foo struct {
internal internalFooAPI
}
type internalFoo struct{}
func NewFoo(internal internalFooAPI) Foo {
return Foo{
internal: internal,
}
}
func (foo Foo) Start() (err error) {
data, err := foo.internal.internalBehavior("bar")
if err != nil {
return err
}
fmt.Println(data)
return err
}
func (foo internalFoo) internalBehavior(input string) (output string, err error) {
return output, err
}
# example_test.go
package example
import (
"testing"
"github.com/pkg/errors"
)
type mockInternalFoo struct{}
type mockInternalFooWithErrors struct{}
func (foo mockInternalFoo) internalBehavior(input string) (output string, err error) {
return output, err
}
func (foo mockInternalFooWithErrors) internalBehavior(input string) (output string, err error) {
return output, errors.New("big error")
}
func TestStart(t *testing.T) {
tdata := []struct {
testCase string
expectedAnError bool
foo Foo
}{
{
testCase: "standard_case",
expectedAnError: false,
foo: NewFoo(internalFoo{}),
},
{
testCase: "mock_case",
expectedAnError: false,
foo: NewFoo(mockInternalFoo{}),
},
{
testCase: "error_case",
expectedAnError: true,
foo: NewFoo(mockInternalFooWithErrors{}),
},
}
for _, test := range tdata {
t.Run(test.testCase, func(t *testing.T) {
// The function under test
err := test.foo.Start()
// Assertion 1
if test.expectedAnError == false && err != nil {
t.Error(err.Error())
}
// Assertion 2
if test.expectedAnError == true && err == nil {
t.Error("expected an error, but there was none!")
}
})
}
}
I use the following code which works ok.
This is working example
https://play.golang.org/p/wjvJtDNvJAQ
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
)
type requester interface {
HTTPRequest(c string, i string, mtd string, url string) (p []byte, e error)
}
type impl struct {
client *http.Client
}
// ----This is the function which I need to mock
func (s *ServiceInfo) wrapperFN() {
// Function 1 - get the values
v1, v2 := s.json.parseJson()
// call to http function
s.req.HTTPRequest(v1, v2, "POST", "http://www.mocky.io/v2/5c20eccc2e00005c001e0c84")
}
func (i impl) HTTPRequest(c string, ci string, mtd string, url string) (p []byte, e error) {
req, err := http.NewRequest(mtd, url, nil)
if err != nil {
return nil, err
}
req.SetBasicAuth(c, ci)
res, err := i.client.Do(req)
if err != nil {
return nil, err
}
token, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
defer res.Body.Close()
fmt.Println("success")
return token, nil
}
type parser interface {
parseJson() (string, string)
}
type jsonP struct {
data string
}
func (s jsonP) parseJson() (string, string) {
var result map[string]interface{}
json.Unmarshal([]byte(s.data), &result)
b := result["person"].(map[string]interface{})
for key, value := range b {
return key, value.(string)
}
return "", ""
}
type ServiceInfo struct {
req requester
json parser
}
// When in production pass in concrete implementations.
func NewServiceInfo(http requester, json parser) *ServiceInfo {
return &ServiceInfo{
req: http,
json: json,
}
}
func main() {
httpClient := http.Client{}
js := `{"person":{"p1":"username","p2":"password"},"customers":"10"}`
j := jsonP{data: js}
s := NewServiceInfo(impl{client: &httpClient}, j)
s.wrapperFN()
}
Now i want to test it wrapperFN , what I try I've changed the code to use interface , which works.
This is just example to give a point ( the real code much more complicated)
The problem that I dont understand how to mock function inside wrapperFN like parseJson() , in the real world warpperFN contains several function which I need to mock ,because just calling them in the test will provide error.
How it's best to mock function like parseJson() & HTTPRequest? and assume that inside wrapperFN there is additional functions which is not related...
I need to know if this is the best practice for testing function.
This is the test (which im not sure how to make it right)
package main
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestServiceInfo_wrapperFN(t *testing.T) {
tests := []struct {
name string
s *ServiceInfo
}{
{
name: "wrapper test",
s: &ServiceInfo{},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var testHandler http.Handler
srv := httptest.NewServer(testHandler)
defer srv.Close()
iReq := &impl{
client: srv.Client(),
}
v := &ServiceInfo{http: *iReq}
v.wrapperFN()
})
}
}