Skip to content

Commit

Permalink
Add gRPC API unit test with mock db and redis (#33)
Browse files Browse the repository at this point in the history
  • Loading branch information
raphaeldiscky authored Apr 21, 2024
1 parent 5bb8ffb commit 7fcb5b8
Show file tree
Hide file tree
Showing 4 changed files with 249 additions and 0 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ server:

mock:
mockgen -package mockdb -destination db/mock/store.go github.com/raphaeldiscky/simple-bank/db/sqlc Store
mockgen -package mockwk -destination worker/mock/distributor.go github.com/raphaeldiscky/simple-bank/worker TaskDistributor

proto:
rm -f pb/*.go
Expand Down
23 changes: 23 additions & 0 deletions gapi/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package gapi

import (
"testing"
"time"

db "github.com/raphaeldiscky/simple-bank/db/sqlc"
"github.com/raphaeldiscky/simple-bank/utils"
"github.com/raphaeldiscky/simple-bank/worker"
"github.com/stretchr/testify/require"
)

func NewTestServer(t *testing.T, store db.Store, taskDistributor worker.TaskDistributor) *Server {
config := utils.Config{
TokenSymmetricKey: utils.RandomString(32),
AccessTokenDuration: time.Minute,
}

server, err := NewServer(config, store, taskDistributor)
require.NoError(t, err)

return server
}
164 changes: 164 additions & 0 deletions gapi/rpc_create_user_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
package gapi

import (
"context"
"database/sql"
"fmt"
"reflect"
"testing"

mockdb "github.com/raphaeldiscky/simple-bank/db/mock"
db "github.com/raphaeldiscky/simple-bank/db/sqlc"
"github.com/raphaeldiscky/simple-bank/pb"
"github.com/raphaeldiscky/simple-bank/utils"
"github.com/raphaeldiscky/simple-bank/worker"
mockwk "github.com/raphaeldiscky/simple-bank/worker/mock"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)

type eqCreateUserTxParamsMatcher struct {
arg db.CreateUserTxParams
password string
user db.User
}

func (expected eqCreateUserTxParamsMatcher) Matches(x interface{}) bool {
actualArg, ok := x.(db.CreateUserTxParams)
if !ok {
return false
}

err := utils.CheckPassword(expected.password, actualArg.HashedPassword)
if err != nil {
return false
}

expected.arg.HashedPassword = actualArg.HashedPassword
if !reflect.DeepEqual(expected.arg.CreateUserParams, actualArg.CreateUserParams) {
return false
}

err = actualArg.AfterCreate(expected.user)
return err == nil
}

func (e eqCreateUserTxParamsMatcher) String() string {
return fmt.Sprintf("matches arg %v and password %v", e.arg, e.password)
}

func EqCreateUserTxParams(arg db.CreateUserTxParams, password string, user db.User) gomock.Matcher {
return eqCreateUserTxParamsMatcher{arg, password, user}
}

func randomUser(t *testing.T) (user db.User, password string) {
password = utils.RandomString(6)
hashedPassword, err := utils.HashPassword(password)
require.NoError(t, err)

user = db.User{
Username: utils.RandomOwner(),
HashedPassword: hashedPassword,
FullName: utils.RandomOwner(),
Email: utils.RandomEmail(),
}
return
}

func TestCreateUserAPI(t *testing.T) {
user, password := randomUser(t)

testCases := []struct {
name string
req *pb.CreateUserRequest
buildStubs func(store *mockdb.MockStore, taskDistributor *mockwk.MockTaskDistributor)
checkResponse func(t *testing.T, res *pb.CreateUserResponse, err error)
}{
{
name: "OK",
req: &pb.CreateUserRequest{
Username: user.Username,
Password: password,
FullName: user.FullName,
Email: user.Email,
},
buildStubs: func(store *mockdb.MockStore, taskDistributor *mockwk.MockTaskDistributor) {
arg := db.CreateUserTxParams{
CreateUserParams: db.CreateUserParams{
Username: user.Username,
FullName: user.FullName,
Email: user.Email,
},
}
store.EXPECT().
CreateUserTx(gomock.Any(), EqCreateUserTxParams(arg, password, user)).
Times(1).
Return(db.CreateUserTxResult{User: user}, nil)

taskPayload := &worker.PayloadSendVerifyEmail{
Username: user.Username,
}

taskDistributor.EXPECT().
DistributeTaskSendVerifyEmail(gomock.Any(), taskPayload, gomock.Any()).
Times(1).
Return(nil)
},
checkResponse: func(t *testing.T, res *pb.CreateUserResponse, err error) {
require.NoError(t, err)
require.NotNil(t, res)
createdUser := res.GetUser()
require.Equal(t, user.Username, createdUser.Username)
require.Equal(t, user.FullName, createdUser.FullName)
require.Equal(t, user.Email, createdUser.Email)
},
},
{
name: "InternalError",
req: &pb.CreateUserRequest{
Username: user.Username,
Password: password,
FullName: user.FullName,
Email: user.Email,
},
buildStubs: func(store *mockdb.MockStore, taskDistributor *mockwk.MockTaskDistributor) {
store.EXPECT().
CreateUserTx(gomock.Any(), gomock.Any()).
Times(1).
Return(db.CreateUserTxResult{}, sql.ErrConnDone)

taskDistributor.EXPECT().
DistributeTaskSendVerifyEmail(gomock.Any(), gomock.Any(), gomock.Any()).
Times(0)
},
checkResponse: func(t *testing.T, res *pb.CreateUserResponse, err error) {
require.Error(t, err)
st, ok := status.FromError(err)
require.True(t, ok)
require.Equal(t, st.Code(), codes.Internal)
},
},
}

for i := range testCases {
tc := testCases[i]

t.Run(tc.name, func(t *testing.T) {
storeCtrl := gomock.NewController(t)
defer storeCtrl.Finish()

store := mockdb.NewMockStore(storeCtrl)

taskCtrl := gomock.NewController(t)
taskDistributor := mockwk.NewMockTaskDistributor(taskCtrl)

tc.buildStubs(store, taskDistributor)

server := NewTestServer(t, store, taskDistributor)
res, err := server.CreateUser(context.Background(), tc.req)
tc.checkResponse(t, res, err)
})
}
}
61 changes: 61 additions & 0 deletions worker/mock/distributor.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 7fcb5b8

Please sign in to comment.