Skip to content

単体テスト作成 ​

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())
			}
		})
	}
}