の続きです。
Echoで簡単なAPI、CRUDのCRを作ってみます(DB保存までしませんが)。
https://echo.labstack.com/guide/routing
https://echo.labstack.com/guide/request
https://echo.labstack.com/guide/response
https://echo.labstack.com/guide/testing
Golangのテスト
まずはその前にHTTPのテストが出来るようにします。
package main
import (
"net/http"
"github.com/labstack/echo"
)
func main() {
router := NewRouter()
router.Logger.Fatal(router.Start(":8080"))
}
func NewRouter() *echo.Echo {
e := echo.New()
e.GET("/hello", helloHandler)
return e
}
func helloHandler(c echo.Context) error {
return c.String(http.StatusOK, "Hello")
}
package main
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
func TestHelloHandler(t *testing.T) {
router := NewRouter()
req := httptest.NewRequest("GET", "/hello", nil)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
assert.Equal(t, http.StatusOK, rec.Code)
assert.Equal(t, "Hello, Echo World!!", rec.Body.String())
}
$ docker-compose exec app go test -v
# runtime/cgo
exec: "gcc": executable file not found in $PATH
FAIL _/go/src [build failed]
なんかエラー。
環境変数の変更が必要らしい。
FROM golang:1.13.6-alpine
ENV CGO_ENABLED=0
WORKDIR /go/src
COPY ./src /go/src
RUN apk update && apk add git
$ docker-compose build
$ docker-compose up -d
$ docker-compose exec app go test -v
=== RUN TestHelloHandler
--- FAIL: TestHelloHandler (0.00s)
server_test.go:20:
Error Trace: server_test.go:20
Error: Not equal:
expected: "Hello, Echo World!!"
actual : "Hello"
Diff:
--- Expected
+++ Actual
@@ -1 +1 @@
-Hello, Echo World!!
+Hello
Test: TestHelloHandler
FAIL
exit status 1
FAIL _/go/src 0.004s
もちろんアサーションはエラーになりました。
なのでserver.go
を修正します。
package main
import (
"net/http"
"github.com/labstack/echo"
)
func main() {
router := NewRouter()
router.Logger.Fatal(router.Start(":8080"))
}
func NewRouter() *echo.Echo {
e := echo.New()
e.GET("/hello", helloHandler)
return e
}
func helloHandler(c echo.Context) error {
return c.String(http.StatusOK, "Hello, Echo World!!")
}
$ docker-compose exec app go test -v
=== RUN TestHelloHandler
--- PASS: TestHelloHandler (0.00s)
PASS
ok _/go/src 0.004s
成功しました
API実装
本題です。
APIのテストを書きます。
package main
import (
"net/url"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/labstack/echo"
"github.com/stretchr/testify/assert"
)
func TestUserIndexHandler(t *testing.T) {
router := NewRouter()
req := httptest.NewRequest(http.MethodGet, "/users", nil)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
assert.Equal(t, http.StatusOK, rec.Code)
assert.JSONEq(t, `[{"name": "Taro", "email": "taro@example.com"}, {"name": "Jiro", "email": "jiro@example.com"}]`, rec.Body.String())
}
func TestUserShowHandler(t *testing.T) {
router := NewRouter()
req := httptest.NewRequest(http.MethodGet, "/users/jiro", nil)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
assert.Equal(t, http.StatusOK, rec.Code)
assert.JSONEq(t, `{"name": "Jiro", "email": "jiro@example.com"}`, rec.Body.String())
}
func TestUserCreateHandler(t *testing.T) {
router := NewRouter()
form := make(url.Values)
form.Set("name", "Saburo")
form.Set("email", "saburo@example.com")
req := httptest.NewRequest(http.MethodPost, "/users", strings.NewReader(form.Encode()))
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationForm)
rec := httptest.NewRecorder()
router.ServeHTTP(rec, req)
assert.Equal(t, http.StatusCreated, rec.Code)
assert.JSONEq(t, `{"name": "Saburo", "email": "saburo@example.com"}`, rec.Body.String())
}
$ docker-compose exec app go test -v
=== RUN TestUserIndexHandler
--- FAIL: TestUserIndexHandler (0.00s)
server_test.go:21:
Error Trace: server_test.go:21
Error: Not equal:
expected: 200
actual : 404
Test: TestUserIndexHandler
server_test.go:22:
Error Trace: server_test.go:22
Error: Not equal:
expected: []interface {}([]interface {}{map[string]interface {}{"email":"taro@example.com", "name":"Taro"}, map[string]interface {}{"email":"jiro@example.com", "name":"Jiro"}})
actual : map[string]interface {}(map[string]interface {}{"message":"Not Found"})
Test: TestUserIndexHandler
=== RUN TestUserShowHandler
--- FAIL: TestUserShowHandler (0.00s)
server_test.go:33:
Error Trace: server_test.go:33
Error: Not equal:
expected: 200
actual : 404
Test: TestUserShowHandler
server_test.go:34:
Error Trace: server_test.go:34
Error: Not equal:
expected: map[string]interface {}{"email":"jiro@example.com", "name":"Jiro"}
actual : map[string]interface {}{"message":"Not Found"}
Diff:
--- Expected
+++ Actual
@@ -1,4 +1,3 @@
-(map[string]interface {}) (len=2) {
- (string) (len=5) "email": (string) (len=16) "jiro@example.com",
- (string) (len=4) "name": (string) (len=4) "Jiro"
+(map[string]interface {}) (len=1) {
+ (string) (len=7) "message": (string) (len=9) "Not Found"
}
Test: TestUserShowHandler
=== RUN TestUserCreateHandler
--- FAIL: TestUserCreateHandler (0.00s)
server_test.go:49:
Error Trace: server_test.go:49
Error: Not equal:
expected: 201
actual : 404
Test: TestUserCreateHandler
server_test.go:50:
Error Trace: server_test.go:50
Error: Not equal:
expected: map[string]interface {}{"email":"saburo@example.com", "name":"Saburo"}
actual : map[string]interface {}{"message":"Not Found"}
Diff:
--- Expected
+++ Actual
@@ -1,4 +1,3 @@
-(map[string]interface {}) (len=2) {
- (string) (len=5) "email": (string) (len=18) "saburo@example.com",
- (string) (len=4) "name": (string) (len=6) "Saburo"
+(map[string]interface {}) (len=1) {
+ (string) (len=7) "message": (string) (len=9) "Not Found"
}
Test: TestUserCreateHandler
FAIL
exit status 1
FAIL _/go/src 0.006s
当たり前ですが全部404でエラーになります。
次にserver.go
にAPIを追加します。
package main
import (
"net/http"
"github.com/labstack/echo"
)
func main() {
router := NewRouter()
router.Logger.Fatal(router.Start(":8080"))
}
func NewRouter() *echo.Echo {
e := echo.New()
e.GET("/users", userIndexHandler)
e.GET("/users/:name", userShowHandler)
e.POST("/users", userCreateHandler)
return e
}
type User struct {
Name string `json:"name" form:"name"`
Email string `json:"email" form:"email"`
}
type Users []User
func userIndexHandler(c echo.Context) error {
var users Users
users = append(users, User {
Name: "Taro",
Email: "taro@example.com",
})
users = append(users, User {
Name: "Jiro",
Email: "jiro@example.com",
})
return c.JSON(http.StatusOK, users)
}
func userShowHandler(c echo.Context) error {
var user User
name := c.Param("name")
if name == "taro" {
user = User {
Name: "Taro",
Email: "taro@example.com",
}
} else if name == "jiro" {
user = User {
Name: "Jiro",
Email: "jiro@example.com",
}
}
return c.JSON(http.StatusOK, user)
}
func userCreateHandler(c echo.Context) (err error) {
user := new(User)
if err = c.Bind(user); err != nil {
return
}
return c.JSON(http.StatusCreated, user)
}
$ docker-compose exec app go test -v
=== RUN TestUserIndexHandler
--- PASS: TestUserIndexHandler (0.00s)
=== RUN TestUserShowHandler
--- PASS: TestUserShowHandler (0.00s)
=== RUN TestUserCreateHandler
--- PASS: TestUserCreateHandler (0.00s)
PASS
ok _/go/src 0.006s
テスト成功しました。テストがシンプルすぎるのは見逃してください。
c.Bind(user)
が新鮮に感じます。
普段はPHPを書いていて、そこまで型に厳密ではないので、Golangの型は厳しくも安心感があります。
ただファイルが分かれているけど、server_test.go
の中でserver.go
のNewRouter()
を呼べるというのは、server_test.go
のテスト対象がserver.go
だから?ということでしょうか。
まだまだGolangの道は奥が深そうです。。
参考URL
https://qiita.com/theoden9014/items/ac8763381758148e8ce5
https://qiita.com/JpnLavender/items/21b4574a7513472903ea