Skip to content

単体テスト作成

echoを使ったハンドラのテストを作成します。

作成手順

cf. https://echo.labstack.com/docs/testing

handlerテスト作成

  • user_handler_test.go
go
package handler

import (
	"bytes"
	"context"
	"easyapp/internal/usecase/params"
	"errors"
	"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.LoginIn) params.LoginOut {
	args := m.Called(ctx, in)
	return args.Get(0).(params.LoginOut)
}

func (m *mockUserUsecase) Me(ctx context.Context, in params.MeIn) (params.MeOut, error) {
	args := m.Called(ctx, in)
	return args.Get(0).(params.MeOut), args.Error(1)
}

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.NewLoginIn(
						"nob",
						"passwd",
					),
				).Return(
					params.NewLoginOut(true),
				)
			},
			expectedStatusCode: http.StatusOK,
			expectedBody:       `{"valid": true}`,
		},
		{
			name:               "invalid request",
			requestBody:        `{"name": "nob", "password": "passwd"`, // 末尾の"}"なし
			setupMock:          func(m *mockUserUsecase) {},
			expectedStatusCode: http.StatusBadRequest,
			expectedBody:       `{"message": "Bad request"}`,
		},
	}

	for _, testcase := range tests {

		req := httptest.NewRequest(
			http.MethodPost,
			"/login",
			bytes.NewBuffer([]byte(testcase.requestBody)),
		)
		req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
		rec := httptest.NewRecorder()
		c := echo.New().NewContext(req, rec)

		mockUsecase := new(mockUserUsecase)
		testcase.setupMock(mockUsecase)
		h := NewUserHandler(mockUsecase)

		if assert.NoError(t, h.Login(c)) {
			assert.Equal(t, testcase.expectedStatusCode, rec.Code)
			assert.JSONEq(t, testcase.expectedBody, rec.Body.String())
		}
	}
}

func TestMe(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(
					"Me",
					mock.Anything,
					params.NewMeIn("nob"),
				).Return(
					params.NewMeOut(
						"nob",
						13,
					),
					nil,
				)
			},
			expectedStatusCode: http.StatusOK,
			expectedBody:       `{"name": "nob", "age": 13}`,
		},
		{
			name:         "usecase error",
			requestParam: map[string]string{"name": "nob"},
			setupMock: func(m *mockUserUsecase) {
				m.On(
					"Me",
					mock.Anything,
					params.NewMeIn("nob"),
				).Return(
					*new(params.MeOut),
					errors.New("不正なユーザ名です。"),
				)
			},
			expectedStatusCode: http.StatusInternalServerError,
			expectedBody:       `{"message": "Internal server error"}`,
		},
	}

	for _, testcase := range tests {

		q := make(url.Values)
		q.Set("name", testcase.requestParam["name"])
		req := httptest.NewRequest(http.MethodGet, "/me?"+q.Encode(), nil)
		rec := httptest.NewRecorder()
		c := echo.New().NewContext(req, rec)

		mockUsecase := new(mockUserUsecase)
		testcase.setupMock(mockUsecase)
		h := NewUserHandler(mockUsecase)

		if assert.NoError(t, h.Me(c)) {
			assert.Equal(t, testcase.expectedStatusCode, rec.Code)
			assert.JSONEq(t, testcase.expectedBody, rec.Body.String())
		}
	}
}