如何对将http调用包装到外部服务的处理程序进行单元测试

时间:2017-06-23 17:33:31

标签: unit-testing go

我有一个名为CreateObject的处理函数。此函数同时包含对我不控制的外部API的POST请求。如果我想对它进行单元测试,我遇到的问题是每次运行测试时我都无法将新对象发布到外部服务。所以我想知道是否有办法用Go或任何解决方法来模拟它。

非常感谢。

打包主要

func main() {

router := mux.NewRouter()

router.HandleFunc("/groups", services.CreateObject).Methods("POST")
c := cors.New(cors.Options{
    AllowedOrigins:   []string{"*"},
    AllowCredentials: true,
    AllowedMethods:   []string{"GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"},
    AllowedHeaders:   []string{"*"},
    ExposedHeaders:   []string{"*"},
})

handler := c.Handler(router)

http.ListenAndServe(":3000", handler)

打包对象

  func CreateObject(w http.ResponseWriter, r *http.Request) {



        var newobject Object
        _ = json.NewDecoder(r.Body).Decode(&newobject )

        //Do things

        jsonStr, err := json.Marshal(newobject)
        if err != nil {
            fmt.Println(err)
            return
        }

        req, err := http.NewRequest("POST", ExternalURL+"/object", bytes.NewBuffer(jsonStr))


        if err != nil {
            fmt.Println(err)
            return
        }

        client := &http.Client{}
        resp, err := client.Do(req)
        if err != nil {
            panic(err)
        }
        defer resp.Body.Close()

        body, _ := ioutil.ReadAll(resp.Body)


        if resp.StatusCode < 200 || resp.StatusCode > 299 {
            w.WriteHeader(resp.StatusCode)
            w.Header().Set("Content-Type", "application/json")
            w.Write(body)

        } else {
            w.WriteHeader(201)
        }



}

1 个答案:

答案 0 :(得分:1)

您有几个选择:

1)您可以定义一个具有http.Client导出方法集的接口。然后,您可以创建此类型的包级变量,默认为* http.Client。您不必在CreateObject中使用* http.Client,而是使用此变量。由于它是一个界面,您可以轻松地模拟客户端。界面如下所示:

type HTTPClient interface {
    Do(req *http.Request) (*http.Response, error)
    Get(url string) (resp *http.Response, err error)
    Post(url string, contentType string, body io.Reader) (resp *http.Response, err error)
    PostForm(url string, data url.Values) (resp *http.Response, err error)
    Head(url string) (resp *http.Response, err error)
}

但是,由于您只调用Do(),因此您的模拟只需要为Do定义实际的测试实现。我们经常使用函数字段样式:

type MockClient struct {
    DoFunc func(req *http.Request) (*http.Response, error)
    // other function fields, if you need them
}

func (m MockClient) Do(req *http.Request) (r *http.Response, err error) {
    if m.DoFunc != nil {
        r, err = m.DoFunc(req)
    }
    return
}

// Define the other 4 methods of the HTTPclient as trivial returns

var mockClient HTTPClient = MockClient{
    DoFunc: func(req *http.Request) (*http.Response, error) {
        return nil, nil
    },
}

var mockClientFail HTTPClient = MockClient{
    DoFunc: func(req *http.Request) (*http.Response, error) {
        return nil, fmt.Errorf("failed")
    },
}

2)在localhost端口上站起来自己的HTTP模拟服务器,在测试中,将ExternalURL变量更改为指向它。这允许您实际测试拨号(这使其更像是功能测试而不是单元测试),同时仍然有效地模拟&#34;外部端点。

在任何一种情况下,请确保您还编写了一些回归测试,以确保外部端点仍然按预期工作。

编辑:每dm03514,Go已经内置了模拟HTTP服务器:https://golang.org/pkg/net/http/httptest/