Appearance
単体テスト作成
echoを使ったハンドラのテストを作成します。
作成手順
cf. https://echo.labstack.com/docs/testing
handlerテスト作成
user_handler_test.go
go
package handler
import (
"bytes"
"context"
"easyapp/internal/apperrors"
"easyapp/internal/application/usecase/params"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"github.com/labstack/echo/v5"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
// モックの定義
type mockUserUsecase struct {
mock.Mock
}
func (m *mockUserUsecase) Login(ctx context.Context, in params.LoginInput) (params.LoginOutput, error) {
args := m.Called(ctx, in)
return args.Get(0).(params.LoginOutput), args.Error(1)
}
func (m *mockUserUsecase) GetUser(ctx context.Context, in params.GetUserInput) (params.GetUserOutput, error) {
args := m.Called(ctx, in)
return args.Get(0).(params.GetUserOutput), args.Error(1)
}
// Loginのテスト
func TestLogin(t *testing.T) {
tests := []struct {
name string // テストケース名
requestBody string // リクエストボディ
setupMock func(m *mockUserUsecase) // モック設定
expectedStatusCode int // 期待されるHTTPステータス
expectedBody string // 期待されるレスポンスボディ
}{
{
name: "Success",
requestBody: `{"name": "nob", "password": "passwd"}`,
setupMock: func(m *mockUserUsecase) {
m.On(
"Login",
mock.Anything,
params.NewLoginInput("nob", "passwd"),
).Return(
params.NewLoginOutput(true),
nil,
)
},
expectedStatusCode: http.StatusOK,
expectedBody: `{"valid": true}`,
},
{
name: "BadRequest",
requestBody: `{"name" "nob", "password": "passwd"}`, // json形式が不正
setupMock: func(m *mockUserUsecase) {},
expectedStatusCode: http.StatusBadRequest,
expectedBody: `{"message": "bad request"}`,
},
{
name: "InvalidInput",
requestBody: `{"name": "", "password": "passwd"}`,
setupMock: func(m *mockUserUsecase) {
m.On(
"Login",
mock.Anything,
params.NewLoginInput("", "passwd"),
).Return(
params.LoginOutput{},
apperrors.ErrInvalidInput,
)
},
expectedStatusCode: http.StatusUnprocessableEntity,
expectedBody: `{"message": "invalid input"}`,
},
{
name: "DatabaseError",
requestBody: `{"name": "nob", "password": "passwd"}`,
setupMock: func(m *mockUserUsecase) {
m.On(
"Login",
mock.Anything,
params.NewLoginInput("nob", "passwd"),
).Return(
params.LoginOutput{},
apperrors.ErrDatabaseOperation,
)
},
expectedStatusCode: http.StatusInternalServerError,
expectedBody: `{"message": "database error"}`,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
req := httptest.NewRequest(
http.MethodPost,
"/login",
bytes.NewBuffer([]byte(test.requestBody)),
)
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
rec := httptest.NewRecorder()
c := echo.New().NewContext(req, rec)
mockUsecase := new(mockUserUsecase)
test.setupMock(mockUsecase)
h := NewUserHandler(mockUsecase)
if assert.NoError(t, h.Login(c)) {
assert.Equal(t, test.expectedStatusCode, rec.Code)
assert.JSONEq(t, test.expectedBody, rec.Body.String())
}
})
}
}
// GetUserのテスト
func TestGetUser(t *testing.T) {
tests := []struct {
name string // テストケース名
requestParam map[string]string // リクエストパラメータ
setupMock func(m *mockUserUsecase) // モック設定
expectedStatusCode int // 期待されるHTTPステータス
expectedBody string // 期待されるレスポンスボディ
}{
{
name: "Success",
requestParam: map[string]string{"name": "nob"},
setupMock: func(m *mockUserUsecase) {
m.On(
"GetUser",
mock.Anything,
params.NewGetUserInput("nob"),
).Return(
params.NewGetUserOutput("nob", 13),
nil,
)
},
expectedStatusCode: http.StatusOK,
expectedBody: `{"name": "nob", "age": 13}`,
},
{
name: "InvalidInput",
requestParam: map[string]string{"name": ""},
setupMock: func(m *mockUserUsecase) {
m.On(
"GetUser",
mock.Anything,
params.NewGetUserInput(""),
).Return(
params.GetUserOutput{},
apperrors.ErrInvalidInput,
)
},
expectedStatusCode: http.StatusUnprocessableEntity,
expectedBody: `{"message": "invalid input"}`,
},
{
name: "DatabaseError",
requestParam: map[string]string{"name": "nob"},
setupMock: func(m *mockUserUsecase) {
m.On(
"GetUser",
mock.Anything,
params.NewGetUserInput("nob"),
).Return(
params.GetUserOutput{},
apperrors.ErrDatabaseOperation,
)
},
expectedStatusCode: http.StatusInternalServerError,
expectedBody: `{"message": "database error"}`,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
q := make(url.Values)
q.Set("name", test.requestParam["name"])
req := httptest.NewRequest(http.MethodGet, "/users?"+q.Encode(), nil)
rec := httptest.NewRecorder()
c := echo.New().NewContext(req, rec)
mockUsecase := new(mockUserUsecase)
test.setupMock(mockUsecase)
h := NewUserHandler(mockUsecase)
if assert.NoError(t, h.GetUser(c)) {
assert.Equal(t, test.expectedStatusCode, rec.Code)
assert.JSONEq(t, test.expectedBody, rec.Body.String())
}
})
}
}