Golang ทำ CRUD Service APIs (No Database) พร้อมเขียน Test ด้วย

Sharing is caring!

ช่วงนี้ผู้เขียนกำลังเริ่มเรียนรู้เกี่ยวกับ Golang มาสักระยะ วันนี้ก็เลยอยากจะพามาลงบทเรียนการเริ่มพัฒนา Service API ด้วยภาษา Go โดยบทความนี้จะขอ Coding โดยใช้ Echo Framework สำเร็จเพื่อรวดเร็วในการพัฒนา Web Service API มาเริ่มกันเลย

เตรียมความพร้อมก่อน

  • ต้องการ Go runtime เวอร์ชั่น v1.13 หรือมากกว่า สำหรับ Echo v.4
  • IDE หรือ Editor (VScode,Sublime,Atom) แล้วแต่ความถนัด (Vscode ติดตั้ง Extensions Golang)
  • Postman IDE หรือ Terminal ตามความถนัด เช่นกัน

เริ่มโค๊ดกันเลย

  • initial go project go mod init poolsawat.com-echo-crud
  • download echo framework libs
poolsawat.api@AGC-FVFCR1UWP3YV poolsawat.com-echo-crud % go get github.com/labstack/echo/v4
go: added github.com/labstack/echo/v4 v4.9.0
go: added github.com/labstack/gommon v0.3.1
go: added github.com/mattn/go-colorable v0.1.11
go: added github.com/mattn/go-isatty v0.0.14
go: added github.com/valyala/bytebufferpool v1.0.0
go: added github.com/valyala/fasttemplate v1.2.1
go: added golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
go: added golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f
go: added golang.org/x/sys v0.0.0-20211103235746-7861aae1554b
go: added golang.org/x/text v0.3.7
  • สร้าง models/user.go
package models

type User struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
}
  • สร้าง routes/user.go
package routes

import (
	"net/http"
	"strconv"

	"github.com/labstack/echo/v4"
	"poolsawat.com-echo-crud/models"
)

var (
	seq = 1
)

type RouteUserHandler struct {
	Users map[int]*models.User
}

//----------
// Handlers
//----------

func (h *RouteUserHandler) CreateUser(c echo.Context) error {
	u := &models.User{
		ID: seq,
	}
	if err := c.Bind(u); err != nil {
		return err
	}
	h.Users[u.ID] = u
	seq++
	return c.JSON(http.StatusCreated, u)
}

func (h *RouteUserHandler) GetUser(c echo.Context) error {
	id, _ := strconv.Atoi(c.Param("id"))
	return c.JSON(http.StatusOK, h.Users[id])
}

func (h *RouteUserHandler) UpdateUser(c echo.Context) error {
	u := new(models.User)
	if err := c.Bind(u); err != nil {
		return err
	}
	id, _ := strconv.Atoi(c.Param("id"))
	h.Users[id].Name = u.Name
	return c.JSON(http.StatusOK, h.Users[id])
}

func (h *RouteUserHandler) DeleteUser(c echo.Context) error {
	id, _ := strconv.Atoi(c.Param("id"))
	delete(h.Users, id)
	return c.NoContent(http.StatusNoContent)
}

func (h *RouteUserHandler) GetAllUsers(c echo.Context) error {
	return c.JSON(http.StatusOK, h.Users)
}
  • สร้าง server.go file กำหนดเป็น main package
package main

import (
	"github.com/labstack/echo/v4"
	"github.com/labstack/echo/v4/middleware"
	"poolsawat.com-echo-crud/models"
	"poolsawat.com-echo-crud/routes"
)

func main() {
	e := echo.New()

	// Middleware
	e.Use(middleware.Logger())
	e.Use(middleware.Recover())

	users := map[int]*models.User{}

	route := &routes.RouteUserHandler{users}

	// Routes
	e.GET("/users", route.GetAllUsers)
	e.POST("/users", route.CreateUser)
	e.GET("/users/:id", route.GetUser)
	e.PUT("/users/:id", route.UpdateUser)
	e.DELETE("/users/:id", route.DeleteUser)

	// Start server
	e.Logger.Fatal(e.Start(":1323"))
}
  • ทดสอบ Service APIs ทั้งหมด
  • [POST] /users
curl -X POST \
  -H 'Content-Type: application/json' \
  -d '{"name":"Poolsawat.com 001"}' \
  localhost:1323/users
  • [GET] /users
curl localhost:1323/users
  • [GET] /users/1
curl localhost:1323/users/1
  • [PUT] /users/1
curl -X PUT \
  -H 'Content-Type: application/json' \
  -d '{"name":"Poolsawat.com 001 Updated"}' \
  localhost:1323/users/1
  • [DELETE] /users/1
curl -X DELETE localhost:1323/users/1
  • สุดท้ายอย่าลืมเขียน Test routes/user_test.go
package routes_test

import (
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	"poolsawat.com-echo-crud/routes"

	"github.com/labstack/echo/v4"
	"github.com/stretchr/testify/assert"
	"poolsawat.com-echo-crud/models"
)

func TestUsers(t *testing.T) {

	var (
		mockDB = map[int]*models.User{
			1: &models.User{1, "Poolsawat.com"},
		}
		extectedOne = "{\"id\":1,\"name\":\"Poolsawat.com\"}"
		extectedAll = "{\"1\":{\"id\":1,\"name\":\"Poolsawat.com\"}}"
	)

	t.Run(`should created user success`, func(t *testing.T) {
		// Setup
		e := echo.New()
		req := httptest.NewRequest(http.MethodPost, "/users", strings.NewReader(extectedOne))
		req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
		rec := httptest.NewRecorder()
		c := e.NewContext(req, rec)

		mockRoute := &routes.RouteUserHandler{mockDB}

		// Assertions
		if assert.NoError(t, mockRoute.CreateUser(c)) {
			assert.Equal(t, http.StatusCreated, rec.Code)
			assert.Equal(t, extectedOne, strings.TrimSpace(rec.Body.String()))
		}
	})

	t.Run(`should get all users success`, func(t *testing.T) {
		// Setup
		e := echo.New()
		req := httptest.NewRequest(http.MethodGet, "/users", nil)
		req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
		rec := httptest.NewRecorder()
		c := e.NewContext(req, rec)

		mockRoute := &routes.RouteUserHandler{mockDB}

		// Assertions
		if assert.NoError(t, mockRoute.GetAllUsers(c)) {
			assert.Equal(t, http.StatusOK, rec.Code)
			assert.Equal(t, extectedAll, strings.TrimSpace(rec.Body.String()))
		}
	})

	t.Run(`should get user by id success`, func(t *testing.T) {
		// Setup
		e := echo.New()
		req := httptest.NewRequest(http.MethodGet, "/users", nil)
		req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
		rec := httptest.NewRecorder()
		c := e.NewContext(req, rec)
		c.SetPath("/users/:id")
		c.SetParamNames("id")
		c.SetParamValues("1")

		mockRoute := &routes.RouteUserHandler{mockDB}

		// Assertions
		if assert.NoError(t, mockRoute.GetUser(c)) {
			assert.Equal(t, http.StatusOK, rec.Code)
			assert.Equal(t, extectedOne, strings.TrimSpace(rec.Body.String()))
		}
	})

	t.Run(`should update user success`, func(t *testing.T) {
		// Given
		expected := "{\"id\":1,\"name\":\"Poolsawat.com update\"}"

		// Setup
		e := echo.New()
		req := httptest.NewRequest(http.MethodPut, "/users", strings.NewReader(expected))
		req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
		rec := httptest.NewRecorder()
		c := e.NewContext(req, rec)
		c.SetPath("/users/:id")
		c.SetParamNames("id")
		c.SetParamValues("1")

		mockRoute := &routes.RouteUserHandler{mockDB}

		// Assertions
		if assert.NoError(t, mockRoute.UpdateUser(c)) {
			assert.Equal(t, http.StatusOK, rec.Code)
			assert.Equal(t, expected, strings.TrimSpace(rec.Body.String()))
		}
	})

	t.Run(`should delete user by id success`, func(t *testing.T) {
		// Setup
		e := echo.New()
		req := httptest.NewRequest(http.MethodDelete, "/users", nil)
		req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
		rec := httptest.NewRecorder()
		c := e.NewContext(req, rec)
		c.SetPath("/users/:id")
		c.SetParamNames("id")
		c.SetParamValues("1")

		mockRoute := &routes.RouteUserHandler{mockDB}

		// Assertions
		if assert.NoError(t, mockRoute.DeleteUser(c)) {
			assert.Equal(t, http.StatusNoContent, rec.Code)
			assert.Equal(t, "", strings.TrimSpace(rec.Body.String()))
		}
	})

}
  • รัน test ด้วย go test ./routes/user.go ./routes/user_test.go -v

ขอบคุณที่ติดตามครับ

ใส่ความเห็น

อีเมลของคุณจะไม่แสดงให้คนอื่นเห็น ช่องข้อมูลจำเป็นถูกทำเครื่องหมาย *