interface conversion: interface {} is nil, not map[string]interface {} [recovered] - unit-testing

I try to make unit test for success create user. But, I got a problem to do it, I always fail to make it and always get error 500 or Internal Server Error. Actually, the handler function to create user is work when I run main.go, but the function unit test is doesn't wotk. Here is the code.
package test
func setupDB() *sql.DB {
var (
username = "root"
password = "root"
host = "localhost"
port = "3306"
db_name = "go-fiber-test"
)
dns := fmt.Sprintf("%s:%s#tcp(%s:%s)/%s?parseTime=True", username, password, host, port, db_name)
db, err := sql.Open("mysql", dns)
if err != nil {
panic(err)
}
db.SetMaxIdleConns(10)
db.SetMaxOpenConns(100)
db.SetConnMaxIdleTime(5 * time.Minute)
db.SetConnMaxLifetime(60 * time.Minute)
return db
}
func setupRouter(db *sql.DB) *fiber.App{
validator := validator.New()
userRepository := repository.NewUserRepository()
userService := service.NewUserService(userRepository, db, validator)
userController := controller.NewUserController(userService)
router := route.RouteInit(userController)
return router
}
func truncateUsers(db *sql.DB) {
db.Exec("TRUNCATE users")
}
func TestCreateUsersSuccess(t *testing.T) {
db := setupDB()
router := setupRouter(db)
requestBody := strings.NewReader(`
{
"name": "myname",
"email": "mynamecool329#gmail.com",
"password": "myname!##",
"address": "Jl. nanananaaana",
"phone": "0812388773342"
}
`)
request := httptest.NewRequest("POST", "http://localhost:3000/users/create", requestBody)
response, err := router.Test(request)
helper.PanicIfError(err)
assert.Equal(t, 200, response.StatusCode)
body, err := io.ReadAll(response.Body)
helper.PanicIfError(err)
var responseBody map[string]interface{}
err = json.Unmarshal(body, &responseBody)
helper.PanicIfError(err)
log.Printf("request : %v", request)
log.Printf("response : %v", response)
log.Printf("response body : %v", response.Body)
log.Printf("response body Map: %v", responseBody)
assert.Equal(t, 200, int(responseBody["code"].(float64)))
assert.Equal(t, "SUCCESS", responseBody["status"])
assert.Equal(t, "myname", responseBody["data"].(map[string]interface{})["name"])
}
package route
func RouteInit(userController controller.UserController) *fiber.App {
app := fiber.New()
app.Use(func(c *fiber.Ctx) error {
defer func() {
if err := recover(); err != nil {
exception.ErrorHandler(c, err)
}
}()
return c.Next()
})
app.Get("/users", middleware.AuthMiddleware, userController.FindAll)
app.Get("/users/:userId", userController.FindById)
app.Put("/users/:userId", middleware.AuthMiddleware, userController.Update)
app.Delete("/users/:userId", middleware.AuthMiddleware, userController.Delete)
app.Post("/users/create", userController.Create)
app.Post("/login", userController.Login)
app.Get("/logout", middleware.AuthMiddleware, userController.Logout)
app.Post("/verify", userController.Verify)
return app
}
The output
=== RUN TestCreateUsersSuccess
2023/02/16 11:22:00 request : &{POST http://localhost:3000/users/create HTTP/1.1 1 1 map[Content-Length:[163]] {
{
"name": "myname",
"email": "mynamecool329#gmail.com",
"password": "myname!##",
"address": "Jl. nanananaaana",
"phone": "0812388773342"
}
} <nil> 163 [] false localhost:3000 map[] map[] <nil> map[] 192.0.2.1:1234 http://localhost:3000/users/create <nil> <nil> <nil> <nil>}
2023/02/16 11:22:00 response : &{200 OK 200 HTTP/1.1 1 1 map[Content-Length:[57] Content-Type:[application/json] Date:[Thu, 16 Feb 2023 04:22:00 GMT]] 0xc00037b940 57 [] false false map[] 0xc000113d00 <nil>}
2023/02/16 11:22:00 response body : &{0xc00000edb0 <nil> <nil> false false {0 0} true false false <nil>}
2023/02/16 11:22:00 response body Map: map[code:500 data:<nil> status:INTERNAL SERVER ERROR]
/home/myname/Documents/Go/go-fiber/unit-test/user/create_test.go:99:
Error Trace: /home/myname/Documents/Go/go-fiber/unit-test/user/create_test.go:99
Error: Not equal:
expected: 200
actual : 500
Test: TestCreateUsersSuccess
/home/myname/Documents/Go/go-fiber/unit-test/user/create_test.go:100:
Error Trace: /home/myname/Documents/Go/go-fiber/unit-test/user/create_test.go:100
Error: Not equal:
expected: "SUCCESS"
actual : "INTERNAL SERVER ERROR"
Diff:
--- Expected
+++ Actual
## -1 +1 ##
-SUCCESS
+INTERNAL SERVER ERROR
Test: TestCreateUsersSuccess
--- FAIL: TestCreateUsersSuccess (0.00s)
panic: interface conversion: interface {} is nil, not map[string]interface {} [recovered]
panic: interface conversion: interface {} is nil, not map[string]interface {}
goroutine 6 [running]:
testing.tRunner.func1.2({0x7e5700, 0xc0003c13b0})
/usr/lib/go-1.18/src/testing/testing.go:1389 +0x24e
testing.tRunner.func1()
/usr/lib/go-1.18/src/testing/testing.go:1392 +0x39f
panic({0x7e5700, 0xc0003c13b0})
/usr/lib/go-1.18/src/runtime/panic.go:838 +0x207
go_fiber/unit-test/user.TestCreateUsersSuccess(0x0?)
/home/myname/Documents/Go/go-fiber/unit-test/user/create_test.go:101 +0x425
testing.tRunner(0xc00037c9c0, 0x87f6b8)
/usr/lib/go-1.18/src/testing/testing.go:1439 +0x102
created by testing.(*T).Run
/usr/lib/go-1.18/src/testing/testing.go:1486 +0x35f
FAIL go_fiber/unit-test/user 0.027s
How to fix that problem?

Related

Localstack: AWS was not able to validate the provided access credentials

When trying to use EC2 client, I get the following error:
=== RUN TestEc2
time="2022-06-10T14:18:43-07:00" level=info msg="starting localstack"
time="2022-06-10T14:18:44-07:00" level=info msg="waiting for localstack to start..."
time="2022-06-10T14:19:01-07:00" level=info msg="localstack: finished waiting"
all_test.go:305: operation error EC2: RunInstances, https response error StatusCode: 401, RequestID: 080f9f11-67d7-4061-86d3-e581551458c1, api error AuthFailure: AWS was not able to validate the provided access credentials
--- FAIL: TestEc2 (18.47s)
FAIL
FAIL command-line-arguments 19.197s
FAIL
func TestEc2(t *testing.T) {
ls, err := localstack.NewInstance()
if err != nil {
log.Fatalf("could not connect to docker: %v", err)
}
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute)
defer cancel()
if err := ls.StartWithContext(ctx); err != nil {
log.Fatalf("could not start Localstack: %v", err)
}
ec2Client = ec2.NewFromConfig(awsConfig(ctx, ls, localstack.EC2))
createInstance(t, ec2Client)
}
func awsConfig(ctx context.Context, l *localstack.Instance, s localstack.Service) aws.Config {
rf := aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...interface{}) (aws.Endpoint, error) {
return aws.Endpoint{
PartitionID: "aws",
URL: l.EndpointV2(s),
SigningRegion: endpoints.UsEast1RegionID,
}, nil
})
cfg, err := config.LoadDefaultConfig(ctx,
config.WithRegion("us-east-1"),
config.WithEndpointResolverWithOptions(rf),
config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider("dummy", "dummy", "dummy")),
)
if err != nil {
log.Fatalf("could not load config: %v", err)
}
return cfg
}
func createInstance(t *testing.T, c *ec2.Client) types.Instance {
t.Helper()
result, err := c.RunInstances(
context.TODO(),
&ec2.RunInstancesInput{
ImageId: aws.String("ami-e7527ed7"),
InstanceType: types.InstanceTypeT2Micro,
MinCount: aws.Int32(1),
MaxCount: aws.Int32(1),
})
if err != nil {
t.Fatal(err)
}
if result == nil {
t.Fatal("empty RunInstance result")
}
if len(result.Instances) != 1 {
t.Fatalf("exptected 1 instance, got %d", len(result.Instances))
}
return result.Instances[0]
}
But if instead I use the deprecated EndpointResolverFunc everything seem to work OK
r := aws.EndpointResolverFunc(func(service, region string) (aws.Endpoint, error) {
return aws.Endpoint{
PartitionID: "aws",
URL: l.EndpointV2(s),
SigningRegion: endpoints.UsEast1RegionID,
}, nil
})
cfg, err := config.LoadDefaultConfig(ctx,
....
config.WithEndpointResolver(r),
)
Anyone has any idea what I might be missing?

Check dynamic value for field in struct in go using testify

I have a user service that validates the user data and formats it and then calls Firebase service which creates a firebase user and return firebase id and then it pass that user data to repository layer. My user struct has an ID field is populated by uuid in user service before passing to repository layer. I am mocking the firebase service and repository layer using strecher/testify. But the test is failing since the ID is populated by service layer and I cannot pass the ID to the user data used by mock function.
user := model.User{
ID: "",
FirebaseID: "",
FirstName: "Test",
LastName: "User",
FullName: "Test User",
Email: "testuser#email.com"
Password: "password",
}
Service layer code
func (u userService) CreateUser(user model.User) error {
err := validateFields(user)
if err != nil {
return fmt.Errorf("userService CreateUser: %w", err)
}
user.FullName = user.FirstName + " " + user.LastName
user.FirebaseID, err = u.authClient.CreateUser(user)
if err != nil {
return fmt.Errorf("userService CreateUser: %w", err)
}
user.ID = uuid.NewString()
err = u.userRepo.CreateUser(user)
if err != nil {
return fmt.Errorf("userService CreateUser: %w", err)
}
return nil
}
Test code
func TestCreateUser(t *testing.T) {
mockFirebaseAuthClient := new(MockFirebaseAuthClient)
mockPostgresRepo := new(MockPostgresRepo)
userService := NewUserService(mockPostgresRepo, mockFirebaseAuthClient)
t.Run("Valid data", func(t *testing.T) {
user := model.User{
ID: "",
FirebaseID: "firebaseuniqueid",
FirstName: "Test",
LastName: "User",
FullName: "Test User",
Email: "testuser#email.com",
Password: "password",
}
mockFirebaseAuthClient.On("CreateUser", user).Return("firebaseuniqueid", nil)
mockPostgresRepo.On("CreateUser", user).Return(nil)
err := userService.CreateUser(user)
if err != nil {
t.Fatalf("Expectd: nil, got: %v", err)
}
})
Error while testing
mock: Unexpected Method Call
-----------------------------
CreateUser(model.User)
0: model.User{ID:"f87fd2f3-5801-4359-a565-a4eb13a6de37", FirebaseID:"firebaseuniqueid", FirstName:"Test", LastName:"User", FullName:"Test User", Email:"testuser#email.com", Password:"password"}
The closest call I have is:
CreateUser(model.User)
0: model.User{ID:"", FirebaseID:"firebaseuniqueid", FirstName:"Test", LastName:"User", FullName:"Test User", Email:"testuser#email.com", Password:"password"}
Difference found in argument 0:
--- Expected
+++ Actual
## -1,3 +1,3 ##
(model.User) {
- ID: (string) "",
+ ID: (string) (len=36) "f87fd2f3-5801-4359-a565-a4eb13a6de37",
FirebaseID: (string) (len=16) "firebaseuniqueid",
Diff: 0: FAIL: (model.User={f87fd2f3-5801-4359-a565-a4eb13a6de37 firebaseuniqueid Test User Test User testuser#email.com password}) != (model.User={ firebaseuniqueid Test User Test User testuser#email.com password}) [recovered]
Is there any way I could check the dynamically created uuid or ignore the values in the struct in the test?
if you don't want to consider mockFirebaseAuthClient.On("CreateUser", user).Return("firebaseuniqueid", nil) and mockPostgresRepo.On("CreateUser", user).Return(nil) and just want to mock that calls, then you can use mock.Anything as the argument in both the calls instead of user like this mockFirebaseAuthClient.On("CreateUser", mock.Anything).Return("firebaseuniqueid", nil) . So the arguments will not be considerd and the mock calls will return required value.
Regarding your question of
Since uuid is not injected into the service layer, how can it be mocked? It is an imported package.
Like this, first, define an interface with the same method we want to mock
type uuidGen interface {
String() string
}
Then, define a mock type in which we're going to define our method
type mockGen struct{}
Then, define the method on the type
func (u *mockGen) String() string {
return "test"
}
Update CreateUser function to receive a uuidGen parameter that shares the method String() with uuid's package.
func (u userService) CreateUser(uuid uuidGen, user User) error {
err := validateFields(user)
if err != nil {
return fmt.Errorf("userService CreateUser: %w", err)
}
user.FullName = user.FirstName + " " + user.LastName
user.FirebaseID, err = u.authClient.CreateUser(user)
if err != nil {
return fmt.Errorf("authClient CreateUser: %w", err)
}
user.ID = uuid.String()
err = u.userRepo.CreateUser(user)
if err != nil {
return fmt.Errorf("userService CreateUser: %w", err)
}
return nil
}
Now we can write the test like this, see how the 2 methods accept different types that implement the interface uuidGen and can call a method String()
func TestCreateUser(t *testing.T) {
mockFirebaseAuthClient := new(MockFirebaseAuthClient)
mockPostgresRepo := new(MockPostgresRepo)
userService := NewUserService("test", "test")
t.Run("Valid data", func(t *testing.T) {
user := User{
ID: "",
FirebaseID: "firebaseuniqueid",
FirstName: "Test",
LastName: "User",
FullName: "Test User",
Email: "testuser#email.com",
Password: "password",
}
mockFirebaseAuthClient.On("CreateUser", user).Return("firebaseuniqueid", nil)
mockPostgresRepo.On("CreateUser", user).Return(nil)
gen := new(mockGen)
err := userService.CreateUser(gen, user)
if err != nil {
t.Fatalf("Expectd: nil, got: %v", err)
}
realUUID := uuid.New()
err = userService.CreateUser(realUUID, user)
if err != nil {
t.Fatalf("Expectd: nil, got: %v", err)
}
t.Log("Mock UUID:", gen.String()) // prints test
t.Log("Real UUID UUID:", realUUID.String()) // prints a UUID
})
}
Run it with go test -v to see the output of t.Log(...)

httptest ResponseRecorder keeps the old value

I have a handlerAuthentication function that I need to test:
func handlerAuthentication(c *gin.Context) {
session := Session.GetSession(c)
var login Login
err := c.BindJSON(&login)
if err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
client, err := initClient(c, login)
fmt.Println("Error: ",err)
if err != nil {
fmt.Println("There's an error !")
c.JSON(http.StatusUnauthorized, gin.H{"error": ErrorWrongLogin})
return
}
err = (*client).Logout(c)
if err != nil {
return
}
session.Set("username", login.Username)
session.Set("password", login.Password)
err = session.Save()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "an error occurred during the save of the session:" + err.Error()})
return
}
c.JSON(http.StatusOK, "Connected")
}
To do so,I made this:
func TestHandlerAuthentication(t *testing.T) {
UrlOdoo = "https://isi.nc"
resp := httptest.NewRecorder()
gin.SetMode(gin.TestMode)
c, r := gin.CreateTestContext(resp)
r.POST("/test", func(c *gin.Context) {
handlerAuthentication(c)
})
ctrl := gomock.NewController(t)
defer ctrl.Finish()
Odoo = OdooRPC{createMockOdooClient}
client = mock_odoorpc.NewMockOdooClient(ctrl)
client.EXPECT().Authenticate(gomock.Any(), gomock.Any(), invalidUsername, invalidPassword).AnyTimes().Return(fmt.Errorf("invalid login"))
client.EXPECT().Authenticate(gomock.Any(), gomock.Any(), validUsername, validPassword).AnyTimes().Return(nil)
client.EXPECT().Logout(gomock.Any()).AnyTimes().Return(nil)
session = mock_session.NewMockSession(ctrl)
Session = SessionGetter{createMockSession}
session.EXPECT().Set("username", validUsername).AnyTimes().Return()
session.EXPECT().Set("password", validPassword).AnyTimes().Return()
session.EXPECT().Save().AnyTimes().Return(nil)
for name, test := range map[string]struct {
input Login
want int
}{
"valid login": {
input: Login{
Username: validUsername,
Password: validPassword,
},
want: 200,
},
"invalid login": {
input: Login{
Username: invalidUsername,
Password: invalidPassword,
},
want: 401,
},
} {
t.Run(name, func(t *testing.T) {
body, _ := json.Marshal(test.input)
c.Request, _ = http.NewRequest(http.MethodPost, "/test", strings.NewReader(string(body)))
r.ServeHTTP(resp, c.Request)
assert.Equal(t, test.want, resp.Code)
resp.Flush()
})
}
}
The problem I'm facing is that if I do the tests one by (valid login and invalid login), they all pass, but when I do the two tests at the same time, the second test fails.
Here's an exemple of execution of the two tests at the same time:
=== RUN TestHandlerAuthentication
=== RUN TestHandlerAuthentication/valid_login
Error: <nil> //No error, so resp.Code should be equal to 200
=== RUN TestHandlerAuthentication/invalid_login
Error: invalid login //Error, so resp.Code should be equal to 401
There's an error !
main_test.go:394:
Error Trace: main_test.go:394
Error: Not equal:
expected: 401
actual : 200
Test: TestHandlerAuthentication/invalid_login
--- FAIL: TestHandlerAuthentication (0.00s)
--- PASS: TestHandlerAuthentication/valid_login (0.00s)
--- FAIL: TestHandlerAuthentication/invalid_login (0.00s)
Expected :401
Actual :200
As expected, an error occured when the login is invalid, but the resp.Code is still 200.
And if I do the invalid login test first, the resp.Code will still be 401.
Is it happening because the tests are parallelized and the httptest ResponseRecorder doesn't work in parallel ?
Thank you for your help.
Thank you leaf bebop
I needed to initialize a new httptest.ResponseRecorder for each test.
To do so, I move the initialisation to the t.Run(name,func(t *testing.T) function:
func TestHandlerAuthentication(t *testing.T) {
UrlOdoo = "https://isi.nc"
ctrl := gomock.NewController(t)
defer ctrl.Finish()
Odoo = OdooRPC{createMockOdooClient}
client = mock_odoorpc.NewMockOdooClient(ctrl)
client.EXPECT().Authenticate(gomock.Any(), gomock.Any(), invalidUsername, invalidPassword).AnyTimes().Return(fmt.Errorf("invalid login"))
client.EXPECT().Authenticate(gomock.Any(), gomock.Any(), validUsername, validPassword).AnyTimes().Return(nil)
client.EXPECT().Logout(gomock.Any()).AnyTimes().Return(nil)
session = mock_session.NewMockSession(ctrl)
Session = SessionGetter{createMockSession}
session.EXPECT().Set("username", validUsername).AnyTimes().Return()
session.EXPECT().Set("password", validPassword).AnyTimes().Return()
session.EXPECT().Save().AnyTimes().Return(nil)
for name, test := range map[string]struct {
input Login
want int
}{
"valid login": {
input: Login{
Username: validUsername,
Password: validPassword,
},
want: 200,
},
"invalid login": {
input: Login{
Username: invalidUsername,
Password: invalidPassword,
},
want: 401,
},
} {
t.Run(name, func(t *testing.T) {
resp := httptest.NewRecorder()
gin.SetMode(gin.TestMode)
c, r := gin.CreateTestContext(resp)
r.POST("/test", func(c *gin.Context) {
handlerAuthentication(c)
})
body, _ := json.Marshal(test.input)
c.Request, _ = http.NewRequest(http.MethodPost, "/test", strings.NewReader(string(body)))
r.ServeHTTP(resp, c.Request)
assert.Equal(t, test.want, resp.Code)
})
}
}

How to mock a nested client in test

I am building a simple function that calls an API that returns a Post using GraphQL (https://github.com/machinebox/graphql). I wrapped the logic in a service that looks like this:
type Client struct {
gcl graphqlClient
}
type graphqlClient interface {
Run(ctx context.Context, req *graphql.Request, resp interface{}) error
}
func (c *Client) GetPost(id string) (*Post, error) {
req := graphql.NewRequest(`
query($id: String!) {
getPost(id: $id) {
id
title
}
}
`)
req.Var("id", id)
var resp getPostResponse
if err := c.gcl.Run(ctx, req, &resp); err != nil {
return nil, err
}
return resp.Post, nil
}
Now I'd like to add test tables for the GetPost function with a fail case when id is set to empty string which causes an error in the downstream call c.gcl.Run.
What I am struggling with is the way the gcl client can be mocked and forced to return the error (when no real API call happens).
My test so far:
package apiClient
import (
"context"
"errors"
"github.com/aws/aws-sdk-go/aws"
"github.com/google/go-cmp/cmp"
"github.com/machinebox/graphql"
"testing"
)
type graphqlClientMock struct {
graphqlClient
HasError bool
Response interface{}
}
func (g graphqlClientMock) Run(_ context.Context, _ *graphql.Request, response interface{}) error {
if g.HasError {
return errors.New("")
}
response = g.Response
return nil
}
func newTestClient(hasError bool, response interface{}) *Client {
return &Client{
gcl: graphqlClientMock{
HasError: hasError,
Response: response,
},
}
}
func TestClient_GetPost(t *testing.T) {
tt := []struct{
name string
id string
post *Post
hasError bool
response getPostResponse
}{
{
name: "empty id",
id: "",
post: nil,
hasError: true,
},
{
name: "existing post",
id: "123",
post: &Post{id: aws.String("123")},
response: getPostResponse{
Post: &Post{id: aws.String("123")},
},
},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
client := newTestClient(tc.hasError, tc.response)
post, err := client.GetPost(tc.id)
if err != nil {
if tc.hasError == false {
t.Error("unexpected error")
}
} else {
if tc.hasError == true {
t.Error("expected error")
}
if cmp.Equal(post, &tc.post) == false {
t.Errorf("Response data do not match: %s", cmp.Diff(post, tc.post))
}
}
})
}
}
I am not sure if passing the response to the mock like this is the right way to do it. Also, I'm struggling to set the right value to the response, since an interface{} type is passed and I don't know how to convert it to the getPostResponse and set the value to Post there.
Your test cases should not go beyond the implementation. I'm specifically referring to the empty-vs-nonempty input or any kind of input really.
Let's take a look at the code you want to test:
func (c *Client) GetPost(id string) (*Post, error) {
req := graphql.NewRequest(`
query($id: String!) {
getPost(id: $id) {
id
title
}
}
`)
req.Var("id", id)
var resp getPostResponse
if err := c.gcl.Run(ctx, req, &resp); err != nil {
return nil, err
}
return resp.Post, nil
}
Nothing in the implementation above is doing anything based on the id parameter value and therefore nothing in your tests for this piece of code should really care about what input is passed in, if it is irrelevant to the implementation it should also be irrelevant to the tests.
Your GetPost has basically two code branches that are taken based on a single factor, i.e. the "nilness" of the returned err variable. This means that as far as your implementation is concerned there are only two possible outcomes, based on what err value Run returns, and therefore there should only be two test cases, a 3rd or 4th test case would be just a variation, if not an outright copy, of the first two.
Your test client is also doing some unnecessary stuff, the main one being its name, i.e. what you have there is not a mock so calling it that is not helpful. Mocks usually do a lot more than just return predefined values, they ensure that methods are called, in the expected order and with the expected arguments, etc. And actually you don't need a mock here at all so it's a good thing it isn't one.
With that in mind, here's what I would suggest you do with your test client.
type testGraphqlClient struct {
resp interface{} // non-pointer value of the desired response, or nil
err error // the error to be returned by Run, or nil
}
func (g testGraphqlClient) Run(_ context.Context, _ *graphql.Request, resp interface{}) error {
if g.err != nil {
return g.err
}
if g.resp != nil {
// use reflection to set the passed in response value
// (i haven't tested this so there may be a bug or two)
reflect.ValueOf(resp).Elem().Set(reflect.ValueOf(g.resp))
}
return nil
}
... and here are the necessary test cases, all two of them:
func TestClient_GetPost(t *testing.T) {
tests := []struct {
name string
post *Post
err error
client testGraphqlClient
}{{
name: "return error from client",
err: errors.New("bad input"),
client: testGraphqlClient{err: errors.New("bad input")},
}, {
name: "return post from client",
post: &Post{id: aws.String("123")},
client: testGraphqlClient{resp: getPostResponse{Post: &Post{id: aws.String("123")}}},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client := Client{gql: tt.client}
post, err := client.GetPost("whatever")
if !cmp.Equal(err, tt.err) {
t.Errorf("got error=%v want error=%v", err, tt.err)
}
if !cmp.Equal(post, tt.post) {
t.Errorf("got post=%v want post=%v", post, tt.post)
}
})
}
}
... there's a bit of repetition going on here, the need to spell out the post and err twice but that's a small price to pay when compared to a more sophisticated/complicated test setup that would populate the test client from the test case's expected output fields.
Addendum:
If you were to update GetPost in such a way that it checks for the empty id and returns an error before it sends a request to graphql then your initial setup would make much more sense:
func (c *Client) GetPost(id string) (*Post, error) {
if id == "" {
return nil, errors.New("empty id")
}
req := graphql.NewRequest(`
query($id: String!) {
getPost(id: $id) {
id
title
}
}
`)
req.Var("id", id)
var resp getPostResponse
if err := c.gcl.Run(ctx, req, &resp); err != nil {
return nil, err
}
return resp.Post, nil
}
... and updating the test cases accordingly:
func TestClient_GetPost(t *testing.T) {
tests := []struct {
name string
id string
post *Post
err error
client testGraphqlClient
}{{
name: "return empty id error",
id: "",
err: errors.New("empty id"),
client: testGraphqlClient{},
}, {
name: "return error from client",
id: "nonemptyid",
err: errors.New("bad input"),
client: testGraphqlClient{err: errors.New("bad input")},
}, {
name: "return post from client",
id: "nonemptyid",
post: &Post{id: aws.String("123")},
client: testGraphqlClient{resp: getPostResponse{Post: &Post{id: aws.String("123")}}},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
client := Client{gql: tt.client}
post, err := client.GetPost(tt.id)
if !cmp.Equal(err, tt.err) {
t.Errorf("got error=%v want error=%v", err, tt.err)
}
if !cmp.Equal(post, tt.post) {
t.Errorf("got post=%v want post=%v", post, tt.post)
}
})
}
}

I can't read from the environment variable (aws-lambda in Go)

I want to test AWS Lambda in local environment using DynamoDB Local and SAM CLI.
I create a simple user db table(id, name) and I'm trying to get the data.
I run "sam local start-api --env-vars test/env.json". When I access "http://localhost:3000/users/1", an error occured. Error message is below. I can't understand what this error message means. How do I fix this error?
{
"errorMessage": "InvalidParameter: 1 validation error(s) found.\n- minimum field size of 3, GetItemInput.TableName.\n",
"errorType": "ErrInvalidParams"
}
This is my code.
func handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
// Environment variables
endpoint := os.Getenv("DYNAMODB_ENDPOINT")
tableName := os.Getenv("DYNAMODB_TABLE_NAME")
// Request
id, _ := request.PathParameters["id"]
// DynamoDB
sess := session.Must(session.NewSession())
config := aws.NewConfig().WithRegion("ap-northeast-1")
if len(endpoint) > 0 {
config = config.WithEndpoint(endpoint)
}
db := dynamodb.New(sess, config)
response, err := db.GetItem(&dynamodb.GetItemInput{
TableName: aws.String(tableName),
Key: map[string]*dynamodb.AttributeValue{
"Id": {
N: aws.String(string(id)),
},
},
AttributesToGet: []*string{
aws.String("Id"),
aws.String("Name"),
},
ConsistentRead: aws.Bool(true),
ReturnConsumedCapacity: aws.String("NONE"),
})
if err != nil {
return events.APIGatewayProxyResponse{}, err
}
user := User{}
err = dynamodbattribute.Unmarshal(&dynamodb.AttributeValue{M: response.Item}, &user)
if err != nil {
return events.APIGatewayProxyResponse{}, err
}
// Json
bytes, err := json.Marshal(user)
if err != nil {
return events.APIGatewayProxyResponse{}, err
}
return events.APIGatewayProxyResponse{
Body: string(bytes),
StatusCode: 200,
}, nil
}
error message
That's been resolved.
First Problem: can't read from the environment variable
This was because the indent size deviation of template.yaml. Category "Events" and Category "Environment" had to be in line.
Second Problem: error with "Function 'UserGetFunction' timed out after 5 seconds"
This was because "localhost" expression. Rewriting "localhost" in env.json to "xxx.xxx.xxx.xxx" worked fine.("xxx.xxx.xxx.xxx" is ip address of your laptop)