Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Controller用のテストutilを作成 #47

Draft
wants to merge 26 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
fc9f65c
routerの設定
oginoshikibu Sep 6, 2024
fce53f6
Update module name to "backend"
oginoshikibu Sep 6, 2024
e0925ff
Remove unused test code
oginoshikibu Sep 6, 2024
66a7d1e
Update module name to "gomoku"
oginoshikibu Sep 7, 2024
f200ea0
Add login controller and route for login functionality
oginoshikibu Sep 7, 2024
db43a3f
Add FriendsController and route for getting friends
oginoshikibu Sep 7, 2024
cc4dffe
Add database package and models for User and Friend
oginoshikibu Sep 7, 2024
372c513
Add request/response structs for GetFriends and Login
oginoshikibu Sep 7, 2024
5564358
remove redundant return
oginoshikibu Sep 7, 2024
272601e
Refactor import statements to use gorm.io/gorm package
oginoshikibu Sep 7, 2024
3b152b9
write docs
oginoshikibu Sep 12, 2024
cfad2dc
middlewareが存在しないことについて言及
oginoshikibu Sep 12, 2024
ca814bc
誤ってcommitしたファイルの削除
oginoshikibu Sep 12, 2024
aaa8817
MVCにした背景について、一文追加
oginoshikibu Sep 12, 2024
7f702d9
login controllerのテストを作成
oginoshikibu Sep 12, 2024
c8165cf
実行部分の切り出し
oginoshikibu Sep 12, 2024
ea6a53c
test utilを用いるよう変更
oginoshikibu Sep 12, 2024
ddf127f
refactor
oginoshikibu Sep 12, 2024
8e67d43
Bodyをinterfaceにし、構造化jsonを扱えるよう変更
oginoshikibu Sep 12, 2024
a1e18fd
refactor
oginoshikibu Sep 12, 2024
a79475b
comment and refactor
oginoshikibu Sep 14, 2024
495f30a
refactorでunmarshalを消してしまっていたので、復元
oginoshikibu Sep 14, 2024
9a37c11
_test packageに変更
oginoshikibu Sep 14, 2024
63ac826
import 順番の変更
oginoshikibu Sep 14, 2024
4c0e729
ドキュメントにutilを追加
oginoshikibu Sep 14, 2024
4ec5b84
support query params to controller test
oginoshikibu Sep 20, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions backend/controllers/friends_controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package controllers

import (
"net/http"

"github.com/gin-gonic/gin"
)

// FriendsController is a struct to define the friends controller
type FriendsController struct {
}

// NewFriendsController is a function to create a new friends controller
func NewFriendsController() *FriendsController {
return &FriendsController{}
}

// GetFriendsResponse is a struct to define the get friends response
type GetFriendsResponse struct {
Message string `json:"message"`
}

// GetFriends is a function to handle the get friends request
func (controller *FriendsController) GetFriends(c *gin.Context) {

var response GetFriendsResponse
response.Message = "Get friends successful"
c.JSON(http.StatusOK, response)
}
39 changes: 39 additions & 0 deletions backend/controllers/login_controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package controllers

import (
"net/http"

"github.com/gin-gonic/gin"
)

// LoginController is a struct to define the login controller
type LoginController struct {
}

// NewLoginController is a function to create a new login controller
func NewLoginController() *LoginController {
return &LoginController{}
}

// LoginRequest is a struct to define the login request
type LoginRequest struct {
Message string `json:"message" binding:"required"`
}

// LoginResponse is a struct to define the login response
type LoginResponse struct {
Message string `json:"message"`
}

// Login is a function to handle the login request
func (controller *LoginController) Login(c *gin.Context) {
var request LoginRequest
if err := c.ShouldBindJSON(&request); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}

var response LoginResponse
response.Message = "Login successful"
c.JSON(http.StatusOK, response)
}
61 changes: 61 additions & 0 deletions backend/controllers/login_controller_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package controllers_test

import (
"net/http"
"testing"

"github.com/gin-gonic/gin"

"gomoku/controllers"
"gomoku/utils/testutils"
)

func TestLoginController_Login(t *testing.T) {

tests := []testutils.ControllerTestCase{
{
Name: "Test case 1: Success",
Request: testutils.HTTPRequest{
Body: map[string]interface{}{
"message": "Hello",
},
},
Response: testutils.HTTPResponse{
Code: http.StatusOK,
Body: map[string]interface{}{
"message": "Login successful",
},
},
},
{
Name: "Test case 2: Failed with empty",
Request: testutils.HTTPRequest{
Body: map[string]interface{}{},
},
Response: testutils.HTTPResponse{
Code: http.StatusBadRequest,
Body: map[string]interface{}{
"error": "Key: 'LoginRequest.Message' Error:Field validation for 'Message' failed on the 'required' tag",
},
},
},
{
Name: "Test case 3: Failed with number",
Request: testutils.HTTPRequest{
Body: map[string]interface{}{
"message": 123,
},
},
Response: testutils.HTTPResponse{
Code: http.StatusBadRequest,
Body: map[string]interface{}{
"error": "json: cannot unmarshal number into Go struct field LoginRequest.message of type string",
},
},
},
}

testutils.RunControllerTest(t, tests, func() gin.HandlerFunc {
return controllers.NewLoginController().Login
})
}
2 changes: 1 addition & 1 deletion backend/go.mod
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module sample
module gomoku

go 1.22.6

Expand Down
14 changes: 3 additions & 11 deletions backend/main.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
package main

import (
"net/http"
"github.com/gin-gonic/gin"
_ "github.com/lib/pq"
"gomoku/routes"
)

func main() {
r := gin.Default()

r.GET("/", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "Hello World",
})
})
r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
router := routes.GetRouter()
router.Run(":8080")
}
10 changes: 10 additions & 0 deletions backend/models/database/database.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package database

import (
"gorm.io/gorm"
)

// Database struct
type Database struct {
*gorm.DB
}
10 changes: 10 additions & 0 deletions backend/models/friend.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package models

import (
"gorm.io/gorm"
)

// Friend struct
type Friend struct {
gorm.Model
}
10 changes: 10 additions & 0 deletions backend/models/user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package models

import (
"gorm.io/gorm"
)

// User struct
type User struct {
gorm.Model
}
28 changes: 28 additions & 0 deletions backend/routes/router.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package routes

import (
"github.com/gin-gonic/gin"

"gomoku/controllers"
)

func GetRouter() *gin.Engine {
router := gin.Default()

api := router.Group("/api/v1")
{
users := api.Group("/users")
{
loginController := controllers.NewLoginController()
users.POST("/login", loginController.Login)
}

friends := api.Group("/friends")
{
friendsController := controllers.NewFriendsController()
friends.GET("/", friendsController.GetFriends)
}
}

return router
}
136 changes: 136 additions & 0 deletions backend/utils/testutils/controller_testutil.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package testutils

import (
"encoding/json"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"

"github.com/gin-gonic/gin"
)

// ControllerTestCase represents a test case for a controller.
type ControllerTestCase struct {
Name string // Name is the name of the test case.
Request HTTPRequest // Request is the HTTP request to be tested.
Response HTTPResponse // Response is the expected HTTP response.
}

// RunControllerTest is a helper function for testing controller functions in a Gin framework.
// It runs a series of test cases and compares the expected response with the actual response.
// The function takes the following parameters:
// - t: The testing.T object for reporting test failures.
// - tests: A slice of ControllerTestCase structs representing the test cases to run.
// - targetControllerHandler: A function that returns the controller handler function to be tested.
//
// Each test case in the tests slice should have the following fields:
// - Name: The name of the test case.
// - Request: A struct representing the HTTP request to be made.
// - Method: The HTTP method of the request (e.g., "GET", "POST", etc.).
// - Url: The URL of the request.
// - Body: The request body as a JSON object.
// - Query: A map of query parameters to be added to the URL.
//
// - Response: A struct representing the expected HTTP response.
// - Code: The expected HTTP status code.
// - Body: The expected response body as a JSON object.
//
// Example usage:
//
// tests := []ControllerTestCase{
// {
// Name: "Test case 1",
// Request: HTTPRequest{
// Method: http.MethodGet,
// Url: "/api/v1/test",
// Body: nil,
// },
// Response: HTTPResponse{
// Code: http.StatusOK,
// Body: map[string]interface{}{
// "message": "success",
// },
// },
// },
// // Add more test cases here...
// }
// RunControllerTest(t, tests, func() gin.HandlerFunc {
// return NewTestController().TestFunction
// })
func RunControllerTest(t *testing.T, tests []ControllerTestCase, targetController func() gin.HandlerFunc) {

// Helper function to marshal the body as a JSON string.
marshalBody := func(body interface{}) string {
if body == nil {
return ""
}
jsonBody, err := json.Marshal(body)
if err != nil {
t.Errorf("failed to marshal body: %v", err)
return ""
}
return string(jsonBody)
}

for _, tt := range tests {
t.Run(tt.Name, func(t *testing.T) {
// Create a new Gin context for each test case.
response := httptest.NewRecorder()
c, _ := gin.CreateTestContext(response)

// Set the request method, URL, and body.
requestBody, err := json.Marshal(tt.Request.Body)
if err != nil {
t.Errorf("failed to marshal request body: %v", err)
}

// Parse the request URL and add query parameters.
requestUrl, err := url.Parse(tt.Request.Url)
if err != nil {
t.Errorf("failed to parse url: %v", err)
}

parsedRequestQuery := requestUrl.Query()
for key, value := range tt.Request.Query {
parsedRequestQuery.Set(key, value)
}
requestUrl.RawQuery = parsedRequestQuery.Encode()

// Create the request object.
c.Request, err = http.NewRequest(
tt.Request.Method,
requestUrl.String(),
strings.NewReader(string(requestBody)),
)
if err != nil {
t.Errorf("failed to create request: %v", err)
}
c.Request.Header.Set("Content-Type", "application/json")

// Call the target controller function.
targetController()(c)

// Check the response status code and body.
if response.Code != tt.Response.Code {
t.Errorf("got = %v, want %v", response.Code, tt.Response.Code)
} else {
// Unmarshal the response body.
var body interface{}
if err := json.Unmarshal(response.Body.Bytes(), &body); err != nil {
t.Errorf("failed to unmarshal response body: %v", err)
}

// Marshal actual and expected body
actualBody := marshalBody(body)
expectedBody := marshalBody(tt.Response.Body)

// compare body as string
if actualBody != expectedBody {
t.Errorf("got = %v, want %v", actualBody, expectedBody)
}
}
})
}
}
15 changes: 15 additions & 0 deletions backend/utils/testutils/http_struct.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package testutils

// HTTPRequest represents an HTTP request.
type HTTPRequest struct {
Method string
Url string
Body interface{}
Query map[string]string
}

// HTTPResponse represents an HTTP response.
type HTTPResponse struct {
Code int
Body interface{}
}
Loading