为啥运行 mux API 测试时响应正文为空?

Posted

技术标签:

【中文标题】为啥运行 mux API 测试时响应正文为空?【英文标题】:Why is the response body empty when running a test of mux API?为什么运行 mux API 测试时响应正文为空? 【发布时间】:2020-12-02 12:44:05 【问题描述】:

我正在尝试在 Go 中构建和测试一个非常基本的 API,以便在遵循他们的教程后了解有关该语言的更多信息。 API 和定义的四个路由在 Postman 和浏览器中工作,但是当尝试为任何路由编写测试时,ResponseRecorder 没有正文,因此我无法验证它是否正确。

我按照示例here 操作,它可以工作,但是当我为我的路线更改它时,没有响应。

这是我的main.go 文件。

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"

    "github.com/gorilla/mux"
)

// A Person represents a user.
type Person struct 
    ID        string    `json:"id,omitempty"`
    Firstname string    `json:"firstname,omitempty"`
    Lastname  string    `json:"lastname,omitempty"`
    Location  *Location `json:"location,omitempty"`


// A Location represents a Person's location.
type Location struct 
    City    string `json:"city,omitempty"`
    Country string `json:"country,omitempty"`


var people []Person

// GetPersonEndpoint returns an individual from the database.
func GetPersonEndpoint(w http.ResponseWriter, req *http.Request) 
    w.Header().Set("Content-Type", "application/json")
    params := mux.Vars(req)
    for _, item := range people 
        if item.ID == params["id"] 
            json.NewEncoder(w).Encode(item)
            return
        
    
    json.NewEncoder(w).Encode(&Person)


// GetPeopleEndpoint returns all people from the database.
func GetPeopleEndpoint(w http.ResponseWriter, req *http.Request) 
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(people)


// CreatePersonEndpoint creates a new person in the database.
func CreatePersonEndpoint(w http.ResponseWriter, req *http.Request) 
    w.Header().Set("Content-Type", "application/json")
    params := mux.Vars(req)
    var person Person
    _ = json.NewDecoder(req.Body).Decode(&person)
    person.ID = params["id"]
    people = append(people, person)
    json.NewEncoder(w).Encode(people)


// DeletePersonEndpoint deletes a person from the database.
func DeletePersonEndpoint(w http.ResponseWriter, req *http.Request) 
    w.Header().Set("Content-Type", "application/json")
    params := mux.Vars(req)
    for index, item := range people 
        if item.ID == params["id"] 
            people = append(people[:index], people[index+1:]...)
            break
        
    
    json.NewEncoder(w).Encode(people)


// SeedData is just for this example and mimics a database in the 'people' variable.
func SeedData() 
    people = append(people, PersonID: "1", Firstname: "John", Lastname: "Smith", Location: &LocationCity: "London", Country: "United Kingdom")
    people = append(people, PersonID: "2", Firstname: "John", Lastname: "Doe", Location: &LocationCity: "New York", Country: "United States Of America")


func main() 
    router := mux.NewRouter()
    SeedData()
    router.HandleFunc("/people", GetPeopleEndpoint).Methods("GET")
    router.HandleFunc("/people/id", GetPersonEndpoint).Methods("GET")
    router.HandleFunc("/people/id", CreatePersonEndpoint).Methods("POST")
    router.HandleFunc("/people/id", DeletePersonEndpoint).Methods("DELETE")
    fmt.Println("Listening on http://localhost:12345")
    fmt.Println("Press 'CTRL + C' to stop server.")
    log.Fatal(http.ListenAndServe(":12345", router))

这是我的main_test.go 文件。

package main

import (
    "fmt"
    "net/http"
    "net/http/httptest"
    "testing"
)

func TestGetPeopleEndpoint(t *testing.T) 
    req, err := http.NewRequest("GET", "/people", nil)
    if err != nil 
        t.Fatal(err)
    

    // We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response.
    rr := httptest.NewRecorder()
    handler := http.HandlerFunc(GetPeopleEndpoint)

    // Our handlers satisfy http.Handler, so we can call their ServeHTTP method
    // directly and pass in our Request and ResponseRecorder.
    handler.ServeHTTP(rr, req)

    // Trying to see here what is in the response.
    fmt.Println(rr)
    fmt.Println(rr.Body.String())

    // Check the status code is what we expect.
    if status := rr.Code; status != http.StatusOK 
        t.Errorf("handler returned wrong status code: got %v want %v",
            status, http.StatusOK)
    

    // Check the response body is what we expect - Commented out because it will fail because there is no body at the moment.
    // expected := `["id":"1","firstname":"John","lastname":"Smith","location":"city":"London","country":"United Kingdom","id":"2","firstname":"John","lastname":"Doe","location":"city":"New York","country":"United States Of America"]`
    // if rr.Body.String() != expected 
    //  t.Errorf("handler returned unexpected body: got %v want %v",
    //      rr.Body.String(), expected)
    // 

我很感激我可能犯了一个初学者的错误,所以请原谅我。我已经阅读了许多测试 mux 的博客,但看不出我做错了什么。

提前感谢您的指导。

更新

将我的 SeeData 呼叫移至 init() 解决了人们呼叫的主体为空的问题。

func init() 
    SeedData()

但是,我现在在测试特定 id 时没有返回任何正文。

func TestGetPersonEndpoint(t *testing.T) 
    id := 1
    path := fmt.Sprintf("/people/%v", id)
    req, err := http.NewRequest("GET", path, nil)
    if err != nil 
        t.Fatal(err)
    

    // We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response.
    rr := httptest.NewRecorder()
    handler := http.HandlerFunc(GetPersonEndpoint)

    // Our handlers satisfy http.Handler, so we can call their ServeHTTP method
    // directly and pass in our Request and ResponseRecorder.
    handler.ServeHTTP(rr, req)

    // Check request is made correctly and responses.
    fmt.Println(path)
    fmt.Println(rr)
    fmt.Println(req)
    fmt.Println(handler)

    // expected response for id 1.
    expected := `"id":"1","firstname":"John","lastname":"Smith","location":"city":"London","country":"United Kingdom"` + "\n"

    if status := rr.Code; status != http.StatusOK 
        message := fmt.Sprintf("The test returned the wrong status code: got %v, but expected %v", status, http.StatusOK)
        t.Fatal(message)
    

    if rr.Body.String() != expected 
        message := fmt.Sprintf("The test returned the wrong data:\nFound: %v\nExpected: %v", rr.Body.String(), expected)
        t.Fatal(message)
    

将我的 SeedData 调用移至 init() 解决了人们调用的主体为空的问题。

func init() 
    SeedData()

创建一个新的路由器实例解决了访问路由上的变量的问题。

rr := httptest.NewRecorder()
router := mux.NewRouter()
router.HandleFunc("/people/id", GetPersonEndpoint)
router.ServeHTTP(rr, req)

【问题讨论】:

【参考方案1】:

我认为这是因为您的测试不包括路由器,因此未检测到路径变量。来,试试这个

 // main.go
 func router() *mux.Router 
    router := mux.NewRouter()
    router.HandleFunc("/people", GetPeopleEndpoint).Methods("GET")
    router.HandleFunc("/people/id", GetPersonEndpoint).Methods("GET")
    router.HandleFunc("/people/id", CreatePersonEndpoint).Methods("POST")
    router.HandleFunc("/people/id", DeletePersonEndpoint).Methods("DELETE")
    return router
 

在您的测试用例中,从路由器方法启动,如下所示

 handler := router()
 // Our handlers satisfy http.Handler, so we can call their ServeHTTP method
 // directly and pass in our Request and ResponseRecorder.
 handler.ServeHTTP(rr, req)

现在,如果您尝试访问路径变量 id,它应该出现在 mux 返回的映射中,因为当您从 router() 返回的 mux 路由器实例初始化处理程序时,mux 注册了它

 params := mux.Vars(req)
 for index, item := range people 
    if item.ID == params["id"] 
        people = append(people[:index], people[index+1:]...)
        break
    
 

也像你提到的,使用 init 函数进行一次性设置。

 // main.go
 func init()
    SeedData()
 

【讨论】:

非常感谢您的回答。是的,我最终到达了那里。在测试类中为带有变量的路径初始化一个新路由器给了我预期的响应。 很高兴能提供帮助。享受围棋 昨天看了教程,今天早上看了net/http,觉得很不错。 你会喜欢的。我敢肯定。我一直在寻找与 Go 相关的查询和创新

以上是关于为啥运行 mux API 测试时响应正文为空?的主要内容,如果未能解决你的问题,请参考以下文章

为啥当我运行我的 phpunit 测试时,我得到一个空响应,但 Postman 中的相同路由/用户的行为符合预期?

在 Rails 中,为啥我的邮件正文只在我的测试中是空的?

响应正文为空(获取请求)[重复]

API 请求在 Postman 上完美运行,但返回空正文以响应 Mulesoft API

为啥颤振 API 响应正文不在控制台中打印?

邮递员在测试 GET 时返回 200 但正文为空