프로그래밍(Web)/Golang

[바미] Go - RESTful API(POST)

Bami 2020. 12. 17. 16:57
728x90
반응형

GET에 이어서 myapp/app_test.go를 수정 해줍니다.

 

myapp/app_test.go

  package myapp

  import (
    "io/ioutil"
    "net/http"
    "net/http/httptest"
    "testing"

    "github.com/stretchr/testify/assert"
  )

  func TestIndex(t *testing.T) {
    assert := assert.New(t)

    ts := httptest.NewServer(NewHandler())
    defer ts.Close()

    resp, err := http.Get(ts.URL)
    assert.NoError(err)
    assert.Equal(http.StatusOK, resp.StatusCode)
    data, _ := ioutil.ReadAll(resp.Body)
    assert.Equal("Hello World", string(data))
  }

  func TestUsers(t *testing.T) {
    assert := assert.New(t)

    ts := httptest.NewServer(NewHandler())
    defer ts.Close()

    resp, err := http.Get(ts.URL + "/users")
    assert.NoError(err)
    assert.Equal(http.StatusOK, resp.StatusCode)
    data, _ := ioutil.ReadAll(resp.Body)
    assert.Contains(string(data), "Get UserInfo")
  }

  func TestGetUserInfo(t *testing.T) {
    assert := assert.New(t)

    ts := httptest.NewServer(NewHandler())
    defer ts.Close()

    resp, err := http.Get(ts.URL + "/users/89")
    assert.NoError(err)
    assert.Equal(http.StatusOK, resp.StatusCode)
    data, _ := ioutil.ReadAll(resp.Body)
    assert.Contains(string(data), "User Id:89")

    resp, err = http.Get(ts.URL + "/users/56")
    assert.NoError(err)
    assert.Equal(http.StatusOK, resp.StatusCode)
    data, _ = ioutil.ReadAll(resp.Body)
    assert.Contains(string(data), "User Id:56")
  }

  func TestCreateUserInfo(t *testing.T) {
    assert := assert.New(t)

    ts := httptest.NewServer(NewHandler())
    defer ts.Close()

    resp, err := http.Post(resp, err := http.Post(ts.URL+"/users", "application/json",
        strings.NewReader(`{"first_name":"changbeom", "last_name":"song", "email":"changbeom2@naver.com"}`)) // 1
    assert.NoError(err)
    assert.Equal(http.StatusCreated, resp.StatusCode)
  }

1. Post로 보낼 때 인자가 3개인데 URL, contentType, body이고, responseresponse, error가 나옵니다.

test server의 URL과 contentType은 JSON, body는 지난번에 JSON 보낸 것 처럼 strings.NewReader로 작성해줍니다.

package myapp

import (
"fmt"
"net/http"

"github.com/gorilla/mux"
)

type User struct { // 4
  ID        int       `json:"id"`
    FirstName string    `json:"first_name"`
  LastName  string    `json:"last_name"`
  Email     string    `json:"email"`
  CreatedAt time.Time `json:"created_at"`
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello World")
}

func usersHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Get UserInfo by /users/{id}")
}

func getUserInfoHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
fmt.Fprint(w, "User Id:", vars["id"])
}

func createUserHandler(w http.ResponseWriter, r *http.Request) { // 3
user := new(User)
json.NewDecoder(r.Body).Decode(user)
if err != nil {
  w.WriteHeader(http.StatusBadRequest)
  fmt.Fprint(w, err)
  return
}
// Created User
user.ID = 2
user.CreateAt = time.Now()
w.WriteHeader(http.StatusCreated)
data, _ := json.Marshal(user)
fmt.Fprint(w, string(data))
}

// NewHandler make a new myapp handler
func NewHandler() http.Handler {
mux := mux.NewRouter()

mux.HandleFunc("/", indexHandler)
mux.HandleFunc("/users", usersHandler).Methods("GET") // 1
mux.HandleFunc("/users", createUserHandler).Methods("POST") // 2
mux.HandleFunc("/users/{id:[0-9]+}", getUserInfoHandler)
return mux
}

1. 그리고 같은 경로라도 어떤 메소드를 보내냐에 따라서 Handler가 달라져야 하기 때문에


myapp/app.go의 코드를 수정 해줍니다!

myapp/app.go

1 : GET 메소드로 들어왔을 때 usersHandler가 실행 되도록 한다. <br />
2 : POST 메소드로 들어왔을 때 createUserHandler가 실행 되도록 한다. <br />
3 : createUserHandler부분이다. err가 nil이 아니면 에러처리, nil이 아니면 Create해준다. <br />
4 : 클라이언트가 JSON으로 보낼 때 JSON을 읽어드려야 하는데 그러기 위해 User struct를 만들어준다. <br />

 그 후 터미널을 열고 go test로 실행하여 테스트를 진행해보자! <br />

<p align = "center"> <img src = "https://user-images.githubusercontent.com/33046341/93309345-5ff87280-f83e-11ea-8ed1-b355d4f33de8.png" width = 70%> </img></p>
PASS를 확인 할 수 있다.

이제 <code>myapp/app_test.go</code>로 넘어와서 제대로 데이터가 넘어왔는지 TestCreateUserInfo()부분을 수정해준다.

``` Go

  func TestCreateUserInfo(t *testing.T) {
    assert := assert.New(t)

    ts := httptest.NewServer(NewHandler())
    defer ts.Close()

    resp, err := http.Post(resp, err := http.Post(ts.URL+"/users", "application/json",
        strings.NewReader(`{"first_name":"changbeom", "last_name":"song", "email":"changbeom2@naver.com"}`)) // 1
    assert.NoError(err)
    assert.Equal(http.StatusCreated, resp.StatusCode)
    // 수정 된 부분.
    user := new(User)
    err := json.NewDecoder(resp.Body).Decode(user)
    assert.NoError(err)
    assert.NotEqual(0, user.ID)

    id := user.ID
    resp, err = http.Get(ts.URL + "/users/" + strconv.Itoa(id)) // 1
    assert.NoError(err)
    assert.Equal(http.StatusOK, resp.StatusCode)

    user2 := new(User)
    err := json.NewDecoder(resp.Body).Decode(user2)
    assert.NoError(err)
    assert.Equal(user.ID, user2.ID)
    assert.Equal(user.FirstName, user2.FirstName)
  }

1 : id값이 int값이기 때문에 string형으로 바꾸어주기 위해 strconv.Itoa(id)를 사용 했습니다.

여기서 user2를 넣어주는 이유는 resp.Body는 user의 info가 와야 하는데 JSON포맷으로 와야하기 때문에 넣어주었고,

user와 user2는 사실상 같은 것이기 때문에 assert.Equal(user.ID, user2.ID) , assert.Equal(user.FirstName, user2.FirstName)를 사용하여 비교해주었습니다.

 

이제 실행을 해봅시다!

이런.. 83번째 줄에서 에러가 났네요...

err := json.NewDecoder(resp.Body).Decode(user2) 여기서 decode하는데 첫번째 글자가 'U'가 왔다는 의미 입니다.

strings.NewReader({"first_name":"changbeom", "last_name":"song", "email":"changbeom2@naver.com"}))

이 포맷을 보면 '{'가 먼저 와야 하는데 포맷이 안 맞아서 생긴 오류입니다.

우리가 아직 유저 정보를 주는 것을 구현을 하지 않았기 때문에 myapp/app.go에 있는

getUserInfoHandler(w http.ResponseWriter, r *http.Request)에서

fmt.Fprint(w, "User Id:", vars["id"])의 'U'가 나온것입니다.

 

이 부분을 바꾸어 주어야 하는데 우선 테스트가 통과 되도록 수정 해봅시다.

myapp/app.go

  var userMap map[int]*User
  var lastID int
...
  func getUserInfoHandler(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r) // 1
    id, err := strconv.Atoi(vars["id"]) // 2
    if err != nil { // 3
      w.WriteHeader(http.StatusBadRequest)
      fmt.Fprint(w, err)
      return
    }
    user, ok := userMap[id] // 4
    if !ok { // 5
      w.WriteHeader(http.StatusOK)
      fmt.Fprint(w, "No User Id:", id)
      return
    }

    w.Header().Add("Content-Type", "application/json")
    w.WriteHeader(http.StatusOK)
    data, _ := json.Marshal(user)
    fmt.Fprint(w, string(data))
 }

func NewHandler() http.Handler {
    userMap = make(map[int]*User)
    lastID = 0
    mux := mux.NewRouter()

    mux.HandleFunc("/", indexHandler)
    mux.HandleFunc("/users", usersHandler).Methods("GET")
    mux.HandleFunc("/users", createUserHandler).Methods("POST")
    mux.HandleFunc("/users/{id:[0-9]+}", getUserInfoHandler)
    return mux
}

1 : 파싱한 값을 가져 옵니다.
2 : vars["id"]값이 string이기 때문에 string -> int로 바꾸어 주기 위해 사용했습니다.
3 : error가 있을 시 처리 부분입니다.
4 : id에 해당하는 값이 map에 있는지 확인해줍니다.
5 : 있으면 true 없으면 false.

 

기존에는 고정으로 사용했지만 user map을 하나 만들어서 create했던 user정보를 등록하고 있다가

그 유저 정보의 data가 있으면 그것을 return하고, 없으면 No User ID: id값이 나오도록 변경했습니다.

그 후myapp/app_test.go부분에서 TestGetUserInfo(t *testing.T)를 수정해줍니다.

func TestGetUserInfo(t *testing.T) {
     assert := assert.New(t)

     ts := httptest.NewServer(NewHandler())
     defer ts.Close()

    resp, err := http.Get(ts.URL + "/users/89")
    assert.NoError(err)
    assert.Equal(http.StatusOK, resp.StatusCode)
    data, _ := ioutil.ReadAll(resp.Body)
    assert.Contains(string(data), "No User Id:89")

    resp, err = http.Get(ts.URL + "/users/56")
    assert.NoError(err)
    assert.Equal(http.StatusOK, resp.StatusCode)
    data, _ = ioutil.ReadAll(resp.Body)
    assert.Contains(string(data), "No User Id:56")
}

아직까지 89번의 userId를 만든적이 없기 떄문에 없는 id라고 뜨게 해야 합니다.

 

이제 실행을 해봅시다!

 

go test 명령어를 통해 테스트를 해보면 통과가 되었음을 알 수 있습니다.

이제 클라이언트 앱으로 등록을 한 뒤 확인을 해봅시다!

지금까지 한 것을 리마인드 해보면 특정 id에 해당하는 유저의 정보를 가져오는 것, 그게 없다면 No User id가 출력 되도록 하는 것,
POST로 정보를 JSON 형태로 보냈을 때 이 정보를 서버가 받아서 새로운 등록을 해서 그 유저의 정보를 return하게 했고, 해당 유저의 정보의 기록을 map에 기록을 했고, 그 뒤 유저가 등록되어 있어 GET으로 /users/에 똑같이 id를 붙여 보낼 시 해당하는 유저의 정보를 JSON으로 보내준 것이기 때문에 user2로 파싱을 해서 테스팅 할 수 있게 했습니다.

풀소스


myapp/app_test.go

  package myapp

  import (
    "encoding/json"
    "io/ioutil"
    "net/http"
    "net/http/httptest"
    "strconv"
    "strings"
    "testing"

    "github.com/stretchr/testify/assert"
  )

  func TestIndex(t *testing.T) {
    assert := assert.New(t)

    ts := httptest.NewServer(NewHandler())
    defer ts.Close()

    resp, err := http.Get(ts.URL)
    assert.NoError(err)
    assert.Equal(http.StatusOK, resp.StatusCode)
    data, _ := ioutil.ReadAll(resp.Body)
    assert.Equal("Hello World", string(data))
  }

  func TestUsers(t *testing.T) {
    assert := assert.New(t)

    ts := httptest.NewServer(NewHandler())
    defer ts.Close()

    resp, err := http.Get(ts.URL + "/users")
    assert.NoError(err)
    assert.Equal(http.StatusOK, resp.StatusCode)
    data, _ := ioutil.ReadAll(resp.Body)
    assert.Contains(string(data), "Get UserInfo")
  }

  func TestGetUserInfo(t *testing.T) {
    assert := assert.New(t)

    ts := httptest.NewServer(NewHandler())
    defer ts.Close()

    resp, err := http.Get(ts.URL + "/users/89")
    assert.NoError(err)
    assert.Equal(http.StatusOK, resp.StatusCode)
    data, _ := ioutil.ReadAll(resp.Body)
    assert.Contains(string(data), "No User Id:89")

    resp, err = http.Get(ts.URL + "/users/56")
    assert.NoError(err)
    assert.Equal(http.StatusOK, resp.StatusCode)
    data, _ = ioutil.ReadAll(resp.Body)
    assert.Contains(string(data), "No User Id:56")
  }

  func TestCreateUser(t *testing.T) {
    assert := assert.New(t)

    ts := httptest.NewServer(NewHandler())
    defer ts.Close()

    resp, err := http.Post(ts.URL+"/users", "application/json",
      strings.NewReader(`{"first_name":"changbeom", "last_name":"song", "email":"changbeom@naver.com"}`))
    assert.NoError(err)
    assert.Equal(http.StatusCreated, resp.StatusCode)

    user := new(User)
    err = json.NewDecoder(resp.Body).Decode(user)
    assert.NoError(err)
    assert.NotEqual(0, user.ID)

    id := user.ID
    resp, err = http.Get(ts.URL + "/users/" + strconv.Itoa(id))
    assert.NoError(err)
    assert.Equal(http.StatusOK, resp.StatusCode)

    user2 := new(User)
    err = json.NewDecoder(resp.Body).Decode(user2)
    assert.NoError(err)
    assert.Equal(user.ID, user2.ID)
    assert.Equal(user.FirstName, user2.FirstName)
  }

myapp/app.go

  package myapp

  import (
    "encoding/json"
    "fmt"
    "net/http"
    "strconv"
    "time"

    "github.com/gorilla/mux"
  )

  // User struct
  type User struct {
    ID        int       `json:"id"`
    FirstName string    `json:"first_name"`
    LastName  string    `json:"last_name"`
    Email     string    `json:"email"`
    CreatedAt time.Time `json:"created_at"`
  }

  var userMap map[int]*User
  var lastID int

  func indexHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "Hello World")
  }

  func usersHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "Get UserInfo by /users/{id}")
  }

  func getUserInfoHandler(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r)
    id, err := strconv.Atoi(vars["id"])
    if err != nil {
      w.WriteHeader(http.StatusBadRequest)
      fmt.Fprint(w, err)
      return
    }
    user, ok := userMap[id]
    if !ok {
      w.WriteHeader(http.StatusOK)
      fmt.Fprint(w, "No User Id:", id)
      return
    }

    w.Header().Add("Content-Type", "application/json")
    w.WriteHeader(http.StatusOK)
    data, _ := json.Marshal(user)
    fmt.Fprint(w, string(data))
  }

  func createUserHandler(w http.ResponseWriter, r *http.Request) {
    user := new(User)
    err := json.NewDecoder(r.Body).Decode(user)
    if err != nil {
      w.WriteHeader(http.StatusBadRequest)
      fmt.Fprint(w, err)
      return
    }

    // Created User
    lastID++
    user.ID = lastID
    user.CreatedAt = time.Now()
    userMap[user.ID] = user

    w.Header().Add("Content-Type", "application/json")
    w.WriteHeader(http.StatusCreated)
    data, _ := json.Marshal(user)
    fmt.Fprint(w, string(data))
  }

  // NewHandler make a new myapp handler
  func NewHandler() http.Handler {
    userMap = make(map[int]*User)
    lastID = 0
    mux := mux.NewRouter()

    mux.HandleFunc("/", indexHandler)
    mux.HandleFunc("/users", usersHandler).Methods("GET")
    mux.HandleFunc("/users", createUserHandler).Methods("POST")
    mux.HandleFunc("/users/{id:[0-9]+}", getUserInfoHandler)
    return mux
  }
728x90
반응형