ข้ามไปยังเนื้อหา

การทดสอบ Echo Handler

ใน Node คุณมักจะ spin up แอป NestJS/Express กับ Supertest แล้วยิง HTTP request เข้าไป ส่วนใน Go package net/http/httptest ให้คุณสร้าง in-memory HTTP recorder (httptest.ResponseRecorder) ได้โดยไม่ต้อง bind network port เทสจึงรันเร็ว, deterministic และไม่ต้อง cleanup

สิ่งที่ต้องการNode.js / Jest + SupertestGo / testing + httptest
Test runnerJestgo test (stdlib)
HTTP simulationsupertest(app).get('/books')httptest.NewRecorder() + เรียก handler
Assertionexpect(res.statusCode).toBe(200)if res.Code != 200 { t.Errorf(...) }
Mock dependencyJest jest.fn()Interface + test double struct
handler/books_test.go
package handler_test
import (
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/labstack/echo/v4"
"github.com/you/books-api/internal/handler"
)
func TestGetBook_OK(t *testing.T) {
e := echo.New()
req := httptest.NewRequest(http.MethodGet, "/books/1", nil)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
c.SetParamNames("id")
c.SetParamValues("1")
h := handler.NewBookHandler(&fakeBookStore{})
if err := h.Get(c); err != nil {
t.Fatalf("handler returned error: %v", err)
}
if rec.Code != http.StatusOK {
t.Errorf("expected 200, got %d", rec.Code)
}
var body map[string]any
if err := json.NewDecoder(rec.Body).Decode(&body); err != nil {
t.Fatalf("could not decode body: %v", err)
}
if body["title"] != "Go in Action" {
t.Errorf("unexpected title: %v", body["title"])
}
}

ประกาศ interface BookStore ไว้ใน handler package แล้วในเทสค่อย implement interface นั้นด้วย struct ที่คืนข้อมูลแบบควบคุมได้ — ไม่ต้องพึ่งฐานข้อมูลจริง

TypeScript
// Jest — mock service
const mockBooksService = {
findById: jest.fn().mockResolvedValue({
id: '1',
title: 'Go in Action',
}),
};
const module = await Test.createTestingModule({
controllers: [BooksController],
providers: [{ provide: BooksService, useValue: mockBooksService }],
}).compile();
Go
// Go — interface + test double (ไม่ต้องการ mocking library)
// handler/books.go — ประกาศ interface ที่ handler พึ่งพา
type BookStore interface {
FindByID(ctx context.Context, id string) (model.Book, error)
Insert(ctx context.Context, req model.CreateBookRequest) (model.Book, error)
}
// handler/books_test.go — implementation สำหรับ test
type fakeBookStore struct{}
func (f *fakeBookStore) FindByID(_ context.Context, id string) (model.Book, error) {
return model.Book{ID: id, Title: "Go in Action", Author: "Kennedy"}, nil
}
func (f *fakeBookStore) Insert(_ context.Context, req model.CreateBookRequest) (model.Book, error) {
return model.Book{ID: "new-id", Title: req.Title, Author: req.Author}, nil
}
func TestCreateBook_Created(t *testing.T) {
e := echo.New()
body := `{"title":"Go in Action","author":"Kennedy","year":2015}`
req := httptest.NewRequest(http.MethodPost, "/books",
strings.NewReader(body))
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
h := handler.NewBookHandler(&fakeBookStore{})
if err := h.Create(c); err != nil {
t.Fatalf("handler error: %v", err)
}
if rec.Code != http.StatusCreated {
t.Errorf("expected 201, got %d: %s", rec.Code, rec.Body.String())
}
}
func TestGetBook_NotFound(t *testing.T) {
e := echo.New()
req := httptest.NewRequest(http.MethodGet, "/books/999", nil)
rec := httptest.NewRecorder()
c := e.NewContext(req, rec)
c.SetParamNames("id")
c.SetParamValues("999")
h := handler.NewBookHandler(&notFoundStore{})
err := h.Get(c)
// Echo handler คืน HTTPError แทนการเขียนลง rec โดยตรง
var he *echo.HTTPError
if !errors.As(err, &he) {
t.Fatalf("expected HTTPError, got %T", err)
}
if he.Code != http.StatusNotFound {
t.Errorf("expected 404, got %d", he.Code)
}
}
type notFoundStore struct{}
func (n *notFoundStore) FindByID(_ context.Context, _ string) (model.Book, error) {
return model.Book{}, store.ErrNotFound
}
func (n *notFoundStore) Insert(_ context.Context, _ model.CreateBookRequest) (model.Book, error) {
return model.Book{}, nil
}
Terminal window
# รัน test ทั้งหมด
go test ./...
# พร้อม coverage report
go test -cover ./...
# Race detector (สำคัญสำหรับ concurrent handler)
go test -race ./...
# Verbose output
go test -v ./internal/handler/...

รันบนเครื่องของคุณ — test ต้องการ Echo package ใช้ go test ./... จาก project root ของคุณ

package stdlib ใดที่ให้ in-memory HTTP recorder สำหรับทดสอบ Go handler?
จะ mock store dependency ใน Go โดยไม่ใช้ mocking library ภายนอกได้อย่างไร?
flag ใดที่เปิด race detector เมื่อรัน Go test?