in this case I am using
Echo
mongo-driver (MongoDB)
gqlgen graphql type safe
is there any simple example for testing my Mutation and Query ? I do love to get advices for this. I have tried to find but can't found any so far,
thank you :)
Here is an test example using testify package and the gqlgen/client package which is used internally for testing.
GraphQL schema:
type Query {
user(loginname: String!): UserDetail
}
type Mutation {
validateAccessToken(accesstoken: String!): UserEntity
}
type User {
loginname: String
avatarUrl: String
}
type UserEntity {
id: ID!
loginname: String
avatarUrl: String
}
type UserDetail {
loginname: String
avatarUrl: String
githubUsername: String
createAt: String
score: Int
}
graph/resolver/resolver.go:
package resolver
import "github.com/mrdulin/gqlgen-cnode/services"
// This file will not be regenerated automatically.
//
// It serves as dependency injection for your app, add any dependencies you require here.
type Resolver struct {
UserService services.UserService
}
services/user.go:
package services
type UserService interface {
GetUserByLoginname(loginname string) *model.UserDetail
ValidateAccessToken(accesstoken string) *model.UserEntity
}
graph/resolver/root.resolver.go:
package resolver
// This file will be automatically regenerated based on the schema, any resolver implementations
// will be copied through when generating and any unknown code will be moved to the end.
import (
"context"
"github.com/mrdulin/gqlgen-cnode/graph/generated"
"github.com/mrdulin/gqlgen-cnode/graph/model"
)
func (r *mutationResolver) ValidateAccessToken(ctx context.Context, accesstoken string) (*model.UserEntity, error) {
return r.UserService.ValidateAccessToken(accesstoken), nil
}
func (r *queryResolver) User(ctx context.Context, loginname string) (*model.UserDetail, error) {
return r.UserService.GetUserByLoginname(loginname), nil
}
// Mutation returns generated.MutationResolver implementation.
func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResolver{r} }
// Query returns generated.QueryResolver implementation.
func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }
type mutationResolver struct{ *Resolver }
type queryResolver struct{ *Resolver }
Created mock object for UserService:
mocks/userService.go:
package mocks
import (
"github.com/mrdulin/gqlgen-cnode/graph/model"
"github.com/stretchr/testify/mock"
)
type MockedUserService struct {
mock.Mock
}
func (s *MockedUserService) GetUserByLoginname(loginname string) *model.UserDetail {
args := s.Called(loginname)
return args.Get(0).(*model.UserDetail)
}
func (s *MockedUserService) ValidateAccessToken(accesstoken string) *model.UserEntity {
args := s.Called(accesstoken)
return args.Get(0).(*model.UserEntity)
}
graph/resolver/root.resolver_test.go:
package resolver_test
import (
"testing"
"github.com/99designs/gqlgen/client"
"github.com/99designs/gqlgen/graphql/handler"
"github.com/mrdulin/gqlgen-cnode/graph/generated"
"github.com/mrdulin/gqlgen-cnode/graph/model"
"github.com/mrdulin/gqlgen-cnode/graph/resolver"
"github.com/mrdulin/gqlgen-cnode/mocks"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)
var (
loginname = "mrdulin"
avatarURL = "avatar.jpg"
score = 50
createAt = "1900-01-01"
)
func TestMutationResolver_ValidateAccessToken(t *testing.T) {
t.Run("should validate accesstoken correctly", func(t *testing.T) {
testUserService := new(mocks.MockedUserService)
resolvers := resolver.Resolver{UserService: testUserService}
c := client.New(handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: &resolvers})))
ue := model.UserEntity{ID: "123", User: model.User{Loginname: &loginname, AvatarURL: &avatarURL}}
testUserService.On("ValidateAccessToken", mock.AnythingOfType("string")).Return(&ue)
var resp struct {
ValidateAccessToken struct{ ID, Loginname, AvatarUrl string }
}
q := `
mutation {
validateAccessToken(accesstoken: "abc") {
id,
loginname,
avatarUrl
}
}
`
c.MustPost(q, &resp)
testUserService.AssertExpectations(t)
require.Equal(t, "123", resp.ValidateAccessToken.ID)
require.Equal(t, "mrdulin", resp.ValidateAccessToken.Loginname)
require.Equal(t, "avatar.jpg", resp.ValidateAccessToken.AvatarUrl)
})
}
func TestQueryResolver_User(t *testing.T) {
t.Run("should query user correctly", func(t *testing.T) {
testUserService := new(mocks.MockedUserService)
resolvers := resolver.Resolver{UserService: testUserService}
c := client.New(handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: &resolvers})))
u := model.UserDetail{User: model.User{Loginname: &loginname, AvatarURL: &avatarURL}, Score: &score, CreateAt: &createAt}
testUserService.On("GetUserByLoginname", mock.AnythingOfType("string")).Return(&u)
var resp struct {
User struct {
Loginname, AvatarURL, CreateAt string
Score int
}
}
q := `
query GetUser($loginname: String!) {
user(loginname: $loginname) {
loginname
avatarUrl
createAt
score
}
}
`
c.MustPost(q, &resp, client.Var("loginname", "mrdulin"))
testUserService.AssertCalled(t, "GetUserByLoginname", "mrdulin")
require.Equal(t, "mrdulin", resp.User.Loginname)
require.Equal(t, "avatar.jpg", resp.User.AvatarURL)
require.Equal(t, 50, resp.User.Score)
require.Equal(t, "1900-01-01", resp.User.CreateAt)
})
}
test result:
=== RUN TestMutationResolver_ValidateAccessToken
=== RUN TestMutationResolver_ValidateAccessToken/should_validate_accesstoken_correctly
TestMutationResolver_ValidateAccessToken/should_validate_accesstoken_correctly: root.resolvers_test.go:44: PASS: ValidateAccessToken(mock.AnythingOfTypeArgument)
--- PASS: TestMutationResolver_ValidateAccessToken (0.00s)
--- PASS: TestMutationResolver_ValidateAccessToken/should_validate_accesstoken_correctly (0.00s)
=== RUN TestQueryResolver_User
=== RUN TestQueryResolver_User/should_query_user_correctly
--- PASS: TestQueryResolver_User (0.00s)
--- PASS: TestQueryResolver_User/should_query_user_correctly (0.00s)
PASS
ok github.com/mrdulin/gqlgen-cnode/graph/resolver 0.141s
test coverage report:
source code: https://github.com/mrdulin/gqlgen-cnode
Related
I have a problem with testing with Repository Mock Database using testify.
I want to test a service/use case that creates a record database for each iteration. Here is the code:
This code contain mock of the database
mockrepository.go
package service
import(
"errors"
"github.com/stretchr/testify/mock"
)
type TestRepositoryMock struct {
Mock mock.Mock
}
func (repository *TestRepositoryMock) CreateTodo(todo *Todo) error {
arguments := repository.Mock.Called(todo)
if arguments.Get(0) == nil {
return errors.New("Error")
} else {
return nil
}
}
func (repository *TestRepositoryMock) CreateTodoDetail(todo *TodoDetail) error {
arguments := repository.Mock.Called(todo)
if arguments.Get(0) == nil {
return errors.New("Error")
} else if arguments.Get(1) == nil {
return errors.New("Error")
} else {
return nil
}
}
the logic inside this use case is to save TodoDetail for each number of items (NumberOfItems variable)
usecase.go
package service
import(
"strconv"
)
type UseCase interface {
SaveTodo(numberOfItems int) (string, error)
}
func NewUseCase(repo Repository) UseCase {
return &useCase{repo: repo}
}
type useCase struct {
repo Repository
}
func (uc *useCase) SaveTodo(numberOfItems int) (string, error){
todo := Todo{
Title: "this is title",
}
uc.repo.CreateTodo(&todo)
for i := 0; i < numberOfItems; i++ {
todoDetail := TodoDetail{
ID: todo.ID,
Item: "item "+ strconv.Itoa(i),
}
uc.repo.CreateTodoDetail(&todoDetail)
}
return "success", nil
}
package service
import(
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
var repository = &TestRepositoryMock{Mock: mock.Mock{}}
var testService = useCase{repo: repository}
func TestService_SaveTodoSuccess(t *testing.T) {
todo := Todo{
Title: "this is title",
}
todoDetail := TodoDetail{
ID: todo.ID,
Item: "item 1",
}
repository.Mock.On("CreateTodo", &todo).Return(nil)
repository.Mock.On("CreateTodoDetail", &todoDetail).Return(nil).Once()
result, err := testService.SaveTodo(3)
assert.Nil(t, err)
assert.NotNil(t, result)
}
How to test the multiple call for CreateTodoDetail inside for loop?
-SOLVED-
For testing on the loop use case, we should provide numbers of mock data. same as loop iteration.
Example. If use case has 3 iteration loop that calls the repository three times then we must provide 3 data mock.
the code should be like this:
func TestService_SaveTodoSuccess(t *testing.T) {
// Mock Entity Todo
todo := Todo{
Title: "this is title",
}
// Mock Entity TodoDetail
todoDetail1 := TodoDetail{
ID: todo.ID,
Item: "item 0",
}
todoDetail2 := TodoDetail{
ID: todo.ID,
Item: "item 1",
}
todoDetail3 := TodoDetail{
ID: todo.ID,
Item: "item 2",
}
repository.Mock.On("CreateTodo", &todo).Return(todo)
// calls 3 times "CreateTodoDetail" Repository
repository.Mock.On("CreateTodoDetail", &todoDetail1).Return(todoDetail1)
repository.Mock.On("CreateTodoDetail", &todoDetail2).Return(todoDetail2)
repository.Mock.On("CreateTodoDetail", &todoDetail3).Return(todoDetail3)
result, err := testService.SaveTodo(3)
assert.Nil(t, err)
assert.NotNil(t, result)
}
The scenario builds 3 different data on todoDetail to mock 3 times iterations for CreateTodoDetail repository
This question already has answers here:
How do you create a new instance of a struct from its type at run time in Go?
(5 answers)
Closed 9 months ago.
Working example files included at the end.
I have a package to assist in testing api handlers by creating test http contexts.
The issue is in the AssertJSONResponseBody. The problem is that once the concrete type and value are extracted from the interface, it contains a pointer to the original object. Any changes to the extracted object affect the original object. This then makes the following equal comparison useless because essentially they are pointing to the same value.
Here is the struct:
type TestRequest struct {
Recorder *httptest.ResponseRecorder
Context *gin.Context
t *testing.T
}
func (r *TestRequest) AssertJSONResponseBody(expectedObj interface{}, mesgAndArgs ...interface{}) bool {
outObject := reflect.Indirect(reflect.ValueOf(expectedObj)).Addr().Interface()
// Set break point at next line and compare expectedObject with outObject.
// Then, step over the line and watch the values for both objects change.
// When the decoder unmarshals the json the original object is changed
// because of the pointer in the outobject.
err := json.NewDecoder(r.Recorder.Body).Decode(outObject)
if err != nil {
return assert.Error(r.t, err)
}
return assert.Equal(r.t, expectedObj, outObject, mesgAndArgs...)
}
How do I create a new instance of the underlying type without coupling it to the original value via a pointer?
Here are the working example files.
APIHandler/main.go
package main
import (
"fmt"
"log"
"net/http"
"github.com/gin-gonic/gin"
)
const (
JSONBindingError string = "An error occurred binding the request."
EmailRequiredError string = "Email is required."
)
func main() {
router := gin.Default()
v1 := router.Group("/api/v1/contacts")
{
v1.POST("/", CreateContactHandler)
}
router.Run()
fmt.Printf("hello, world\n")
}
func CreateContactHandler(c *gin.Context) {
request := CreateContactRequest{}
err := c.Bind(&request)
if err != nil {
log.Println("ERROR:", JSONBindingError, err)
apiError := APIError{StatusCode: http.StatusBadRequest, Message: JSONBindingError}
c.JSON(http.StatusBadRequest, apiError)
return
}
if request.Contact.Email == "" {
log.Println("ERROR:", http.StatusBadRequest, EmailRequiredError)
apiError := APIError{StatusCode: http.StatusBadRequest, Message: EmailRequiredError}
c.JSON(http.StatusBadRequest, apiError)
return
}
// Successful client request
// resp := h.Client.CreateContact(request)
// c.JSON(resp.StatusCode, resp.Body)
}
type CreateContactRequest struct {
Contact Contact
}
type Contact struct {
Name string
Email string
}
type CreateContactResponse struct {
Message string
}
type APIError struct {
StatusCode int `json:"status"` // Should match the response status code
Message string `json:"message"`
}
type APIResponse struct {
StatusCode int
Body interface{}
}
APIHandler/helpers/http.go
package helpers
import (
"bytes"
"encoding/json"
"net/http/httptest"
"reflect"
"testing"
"github.com/stretchr/testify/assert"
"github.com/gin-gonic/gin"
)
// TestRequest is a struct to facilitate
// HTTP Context testing with gin handlers
type TestRequest struct {
Recorder *httptest.ResponseRecorder
Context *gin.Context
t *testing.T
}
// NewTestRequest returns a new TestRequest
func NewTestRequest(t *testing.T) *TestRequest {
rec := httptest.NewRecorder()
ctx, _ := gin.CreateTestContext(rec)
ctx.Request = httptest.NewRequest("GET", "/", nil)
return &TestRequest{
Recorder: rec,
Context: ctx,
t: t,
}
}
// SetJSONRequestBody returns a new TestRequest where the request is a post.
// Takes an interface to marshal into JSON and set as the request body.
func (r *TestRequest) SetJSONRequestBody(obj interface{}) *TestRequest {
json, err := json.Marshal(obj)
assert.NoError(r.t, err)
r.Context.Request = httptest.NewRequest("POST", "/", bytes.NewBuffer(json))
r.Context.Request.Header.Add("Content-Type", "application/json")
return r
}
// AssertStatusCode asserts that the recorded status
// is the same as the submitted status.
// The message and the args are added to the message
// when the assertion fails.
func (r *TestRequest) AssertStatusCode(expectedCode int, msgAndArgs ...interface{}) bool {
return assert.Equal(r.t, expectedCode, r.Recorder.Code, msgAndArgs...)
}
// AssertJSONResponseBody asserts that the recorded
// response body unmarshals to the given interface.
// The message and the args are added to the message
// when the assertion fails.
func (r *TestRequest) AssertJSONResponseBody(expectedObj interface{}, masgAndArgs ...interface{}) bool {
out := reflect.Indirect(reflect.ValueOf(expectedObj)).Addr().Interface()
err := json.NewDecoder(r.Recorder.Body).Decode(out)
if err != nil {
return assert.Error(r.t, err)
}
return assert.Equal(r.t, expectedObj, out, masgAndArgs...)
}
APIHandler/main_test.go
package main
import (
"APIHandler/helpers"
"net/http"
"testing"
)
func TestSingleContactCreate(t *testing.T) {
tests := []struct {
toCreate interface{}
handlerExpected APIError
clientReturn APIResponse
statusCode int
}{
// when there is a JSON binding error
{toCreate: "",
handlerExpected: APIError{StatusCode: http.StatusBadRequest, Message: EmailRequiredError},
clientReturn: APIResponse{},
statusCode: http.StatusBadRequest},
// when email is missing
{toCreate: CreateContactRequest{
Contact: Contact{
Name: "test",
}},
handlerExpected: APIError{StatusCode: http.StatusBadRequest, Message: JSONBindingError},
clientReturn: APIResponse{},
statusCode: http.StatusBadRequest},
}
// act
for i, test := range tests {
req := helpers.NewTestRequest(t)
req.SetJSONRequestBody(test.toCreate)
CreateContactHandler(req.Context)
// assert
req.AssertStatusCode(test.statusCode, "Test %d", i)
req.AssertJSONResponseBody(&test.handlerExpected, "Test %d", i)
}
}
Here's the resolution per #mkopriva:
func (r *TestRequest) AssertJSONResponseBody(expectedObj interface{}, masgAndArgs ...interface{}) bool {
elem := reflect.TypeOf(expectedObj)
theType := elem.Elem()
newInstance := reflect.New(theType)
out := newInstance.Interface()
err := json.NewDecoder(r.Recorder.Body).Decode(out)
if err != nil {
return assert.Error(r.t, err)
}
return assert.Equal(r.t, expectedObj, out, masgAndArgs...)
}
I am writing unit test in golang by https://github.com/stretchr/testify
Suppose I have a method below,
func DoSomething(result interface{}) error {
// write some data to result
return nil
}
so the caller can call DoSomething as following
result := &SomeStruct{}
err := DoSomething(result)
if err != nil {
fmt.Println(err)
} else {
fmt.Println("The result is", result)
}
Now I know how to use testify or some other mocking tools to mock the returns value (it's err here) by something like
mockObj.On("DoSomething", mock.Anything).Return(errors.New("mock error"))
My question is "how do i mock the result argument" in this kind of scenario?
Since result is not a return value but a argument, the caller calls it by passing a pointer of a struct, and the function modify it.
You can use the (*Call).Run method:
Run sets a handler to be called before returning. It can be used when
mocking a method (such as an unmarshaler) that takes a pointer to a
struct and sets properties in such struct
Example:
mockObj.On("Unmarshal", mock.AnythingOfType("*map[string]interface{}")).Return().Run(func(args Arguments) {
arg := args.Get(0).(*map[string]interface{})
arg["foo"] = "bar"
})
As #bikbah said, here is an example:
services/message.go:
type messageService struct {
HttpClient http.Client
BaseURL string
}
func (m *messageService) MarkAllMessages(accesstoken string) []*model.MarkedMessage {
endpoint := m.BaseURL + "/message/mark_all"
var res model.MarkAllMessagesResponse
if err := m.HttpClient.Post(endpoint, &MarkAllMessagesRequestPayload{Accesstoken: accesstoken}, &res); err != nil {
fmt.Println(err)
return res.MarkedMsgs
}
return res.MarkedMsgs
}
We passes res to the m.HttpClient.Post method. In this method, the res will be populated with json.unmarshal method.
mocks/http.go:
package mocks
import (
"io"
"github.com/stretchr/testify/mock"
)
type MockedHttp struct {
mock.Mock
}
func (m *MockedHttp) Get(url string, data interface{}) error {
args := m.Called(url, data)
return args.Error(0)
}
func (m *MockedHttp) Post(url string, body interface{}, data interface{}) error {
args := m.Called(url, body, data)
return args.Error(0)
}
services/message_test.go:
package services_test
import (
"errors"
"reflect"
"strconv"
"testing"
"github.com/stretchr/testify/mock"
"github.com/mrdulin/gqlgen-cnode/graph/model"
"github.com/mrdulin/gqlgen-cnode/services"
"github.com/mrdulin/gqlgen-cnode/mocks"
)
const (
baseURL string = "http://localhost/api/v1"
accesstoken string = "123"
)
func TestMessageService_MarkAllMessages(t *testing.T) {
t.Run("should mark all messaages", func(t *testing.T) {
testHttp := new(mocks.MockedHttp)
var res model.MarkAllMessagesResponse
var markedMsgs []*model.MarkedMessage
for i := 1; i <= 3; i++ {
markedMsgs = append(markedMsgs, &model.MarkedMessage{ID: strconv.Itoa(i)})
}
postBody := services.MarkAllMessagesRequestPayload{Accesstoken: accesstoken}
testHttp.On("Post", baseURL+"/message/mark_all", &postBody, &res).Return(nil).Run(func(args mock.Arguments) {
arg := args.Get(2).(*model.MarkAllMessagesResponse)
arg.MarkedMsgs = markedMsgs
})
service := services.NewMessageService(testHttp, baseURL)
got := service.MarkAllMessages(accesstoken)
want := markedMsgs
testHttp.AssertExpectations(t)
if !reflect.DeepEqual(got, want) {
t.Errorf("got wrong return value. got: %#v, want: %#v", got, want)
}
})
t.Run("should print error and return empty slice", func(t *testing.T) {
var res model.MarkAllMessagesResponse
testHttp := new(mocks.MockedHttp)
postBody := services.MarkAllMessagesRequestPayload{Accesstoken: accesstoken}
testHttp.On("Post", baseURL+"/message/mark_all", &postBody, &res).Return(errors.New("network"))
service := services.NewMessageService(testHttp, baseURL)
got := service.MarkAllMessages(accesstoken)
var want []*model.MarkedMessage
testHttp.AssertExpectations(t)
if !reflect.DeepEqual(got, want) {
t.Errorf("got wrong return value. got: %#v, want: %#v", got, want)
}
})
}
In the unit test case, we populated the res in #Call.Run method and assigned the return value(res.MarkedMsgs) of service.MarkAllMessages(accesstoken) to got variable.
unit test result and coverage:
=== RUN TestMessageService_MarkAllMessages
--- PASS: TestMessageService_MarkAllMessages (0.00s)
=== RUN TestMessageService_MarkAllMessages/should_mark_all_messaages
TestMessageService_MarkAllMessages/should_mark_all_messaages: message_test.go:39: PASS: Post(string,*services.MarkAllMessagesRequestPayload,*model.MarkAllMessagesResponse)
--- PASS: TestMessageService_MarkAllMessages/should_mark_all_messaages (0.00s)
=== RUN TestMessageService_MarkAllMessages/should_print_error_and_return_empty_slice
network
TestMessageService_MarkAllMessages/should_print_error_and_return_empty_slice: message_test.go:53: PASS: Post(string,*services.MarkAllMessagesRequestPayload,*model.MarkAllMessagesResponse)
--- PASS: TestMessageService_MarkAllMessages/should_print_error_and_return_empty_slice (0.00s)
PASS
coverage: 5.6% of statements in ../../gqlgen-cnode/...
Process finished with exit code 0
I highly recommend to get familiar with the gomock framework and develop towards interfaces. What you need would look something like this.
// SetArg does the job
myObj.EXPECT().DoSomething(gomock.Any()).SetArg(0, <value you want to r eturn>).Return(nil)
https://github.com/golang/mock#building-mocks
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()
})
}
}
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 {
...
}