如何使用httptest在Go中测试http调用

huangapple go评论83阅读模式
英文:

How to test http calls in Go using httptest

问题

我有以下代码:

package main

import (
    "encoding/json"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "time"
)

type twitterResult struct {
    Results []struct {
        Text     string `json:"text"`
        Ids      string `json:"id_str"`
        Name     string `json:"from_user_name"`
        Username string `json:"from_user"`
        UserId   string `json:"from_user_id_str"`
    }
}

var (
  twitterUrl = "http://search.twitter.com/search.json?q=%23UCL"
  pauseDuration = 5 * time.Second
)

func retrieveTweets(c chan<- *twitterResult) {
    for {
        resp, err := http.Get(twitterUrl)
        if err != nil {
            log.Fatal(err)
        }

        defer resp.Body.Close()
        body, err := ioutil.ReadAll(resp.Body)
        r := new(twitterResult) //or &twitterResult{} which returns *twitterResult
        err = json.Unmarshal(body, &r)
        if err != nil {
            log.Fatal(err)
        }
        c <- r
        time.Sleep(pauseDuration)
    }

}

func displayTweets(c chan *twitterResult) {
    tweets := <-c
    for _, v := range tweets.Results {
        fmt.Printf("%v:%v\n", v.Username, v.Text)
    }

}

func main() {
    c := make(chan *twitterResult)
    go retrieveTweets(c)
    for {
        displayTweets(c)
    }

}

我想为它编写一些测试,但我不确定如何使用httptest包http://golang.org/pkg/net/http/httptest/,希望得到一些指导。

我想到了这个(无耻地从go OAuth的测试中复制过来https://code.google.com/p/goauth2/source/browse/oauth/oauth_test.go):

var request = struct {
    path, query       string // request
    contenttype, body string // response
}{
    path:        "/search.json?",
    query:       "q=%23Kenya",
    contenttype: "application/json",
    body:        twitterResponse,
}

var (
    twitterResponse = `{ 'results': [{'text':'hello','id_str':'34455w4','from_user_name':'bob','from_user_id_str':'345424'}]}`
)

func TestRetrieveTweets(t *testing.T) {
    handler := func(w http.ResponseWriter, r *http.Request) {

        w.Header().Set("Content-Type", request.contenttype)
        io.WriteString(w, request.body)
    }

    server := httptest.NewServer(http.HandlerFunc(handler))
    defer server.Close()

    resp, err := http.Get(server.URL)
    if err != nil {
        t.Fatalf("Get: %v", err)
    }
    checkBody(t, resp, twitterResponse)
}

func checkBody(t *testing.T, r *http.Response, body string) {
    b, err := ioutil.ReadAll(r.Body)
    if err != nil {
        t.Error("reading reponse body: %v, want %q", err, body)
    }
    if g, w := string(b), body; g != w {
        t.Errorf("request body mismatch: got %q, want %q", g, w)
    }
}
英文:

I have the following code:

package main

import (
	&quot;encoding/json&quot;
	&quot;fmt&quot;
	&quot;io/ioutil&quot;
	&quot;log&quot;
	&quot;net/http&quot;
	&quot;time&quot;
)

type twitterResult struct {
	Results []struct {
		Text     string `json:&quot;text&quot;`
		Ids      string `json:&quot;id_str&quot;`
		Name     string `json:&quot;from_user_name&quot;`
		Username string `json:&quot;from_user&quot;`
		UserId   string `json:&quot;from_user_id_str&quot;`
	}
}

var (
  twitterUrl = &quot;http://search.twitter.com/search.json?q=%23UCL&quot;
  pauseDuration = 5 * time.Second
)

func retrieveTweets(c chan&lt;- *twitterResult) {
	for {
		resp, err := http.Get(twitterUrl)
		if err != nil {
			log.Fatal(err)
		}

		defer resp.Body.Close()
		body, err := ioutil.ReadAll(resp.Body)
		r := new(twitterResult) //or &amp;twitterResult{} which returns *twitterResult
		err = json.Unmarshal(body, &amp;r)
		if err != nil {
			log.Fatal(err)
		}
		c &lt;- r
		time.Sleep(pauseDuration)
	}

}

func displayTweets(c chan *twitterResult) {
	tweets := &lt;-c
	for _, v := range tweets.Results {
		fmt.Printf(&quot;%v:%v\n&quot;, v.Username, v.Text)
	}

}

func main() {
	c := make(chan *twitterResult)
	go retrieveTweets(c)
	for {
		displayTweets(c)
	}

}

I'd like to write some tests for it, but I'm not sure how to use the httptest package http://golang.org/pkg/net/http/httptest/ would appreciate some pointers

I came up with this (shamelessly copied from the tests for go OAuth https://code.google.com/p/goauth2/source/browse/oauth/oauth_test.go):

var request = struct {
	path, query       string // request
	contenttype, body string // response
}{
	path:        &quot;/search.json?&quot;,
	query:       &quot;q=%23Kenya&quot;,
	contenttype: &quot;application/json&quot;,
	body:        twitterResponse,
}

var (
	twitterResponse = `{ &#39;results&#39;: [{&#39;text&#39;:&#39;hello&#39;,&#39;id_str&#39;:&#39;34455w4&#39;,&#39;from_user_name&#39;:&#39;bob&#39;,&#39;from_user_id_str&#39;:&#39;345424&#39;}]}`
)

func TestRetrieveTweets(t *testing.T) {
	handler := func(w http.ResponseWriter, r *http.Request) {

		w.Header().Set(&quot;Content-Type&quot;, request.contenttype)
		io.WriteString(w, request.body)
	}

	server := httptest.NewServer(http.HandlerFunc(handler))
	defer server.Close()

	resp, err := http.Get(server.URL)
	if err != nil {
		t.Fatalf(&quot;Get: %v&quot;, err)
	}
	checkBody(t, resp, twitterResponse)
}

func checkBody(t *testing.T, r *http.Response, body string) {
	b, err := ioutil.ReadAll(r.Body)
	if err != nil {
		t.Error(&quot;reading reponse body: %v, want %q&quot;, err, body)
	}
	if g, w := string(b), body; g != w {
		t.Errorf(&quot;request body mismatch: got %q, want %q&quot;, g, w)
	}
}

答案1

得分: 96

httptest有两种类型的测试:响应测试和服务器测试。

响应测试:

func TestHeader3D(t *testing.T) {
    resp := httptest.NewRecorder()

    uri := "/3D/header/?&"
    path := "/home/test"
    unlno := "997225821"

    param := make(url.Values)
    param["param1"] = []string{path}
    param["param2"] = []string{unlno}

    req, err := http.NewRequest("GET", uri+param.Encode(), nil)
    if err != nil {
            t.Fatal(err)
    }

    http.DefaultServeMux.ServeHTTP(resp, req)
    if p, err := ioutil.ReadAll(resp.Body); err != nil {
            t.Fail()
    } else {
            if strings.Contains(string(p), "Error") {
                    t.Errorf("header response shouldn't return error: %s", p)
            } else if !strings.Contains(string(p), `expected result`) {
                    t.Errorf("header response doesn't match:\n%s", p)
            }
    }
}

服务器测试(这是你需要使用的):

func TestIt(t *testing.T){
    ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")
        fmt.Fprintln(w, `{"fake twitter json string"}`)
    }))
    defer ts.Close()

    twitterUrl = ts.URL
    c := make(chan *twitterResult)
    go retrieveTweets(c)
    
    tweet := <-c
    if tweet != expected1 {
        t.Fail()
    }
    tweet = <-c
    if tweet != expected2 {
        t.Fail()
    }
}

顺便说一下,你不需要传递r的指针,因为它已经是一个指针。

err = json.Unmarshal(body, r)

编辑:对于我的记录器测试,我可以像这样使用我的http处理程序:

handler(resp, req)

但是我的原始代码没有使用默认的mux(而是来自Gorilla/mux),并且我在mux周围进行了一些包装,例如插入服务器日志和添加请求上下文(Gorilla/context),所以我必须从mux开始并调用ServeHTTP。

英文:

httptest does two types of tests: response and server

Response test:

func TestHeader3D(t *testing.T) {
    resp := httptest.NewRecorder()

    uri := &quot;/3D/header/?&quot;
    path := &quot;/home/test&quot;
    unlno := &quot;997225821&quot;

    param := make(url.Values)
    param[&quot;param1&quot;] = []string{path}
    param[&quot;param2&quot;] = []string{unlno}

    req, err := http.NewRequest(&quot;GET&quot;, uri+param.Encode(), nil)
    if err != nil {
            t.Fatal(err)
    }

    http.DefaultServeMux.ServeHTTP(resp, req)
    if p, err := ioutil.ReadAll(resp.Body); err != nil {
            t.Fail()
    } else {
            if strings.Contains(string(p), &quot;Error&quot;) {
                    t.Errorf(&quot;header response shouldn&#39;t return error: %s&quot;, p)
            } else if !strings.Contains(string(p), `expected result`) {
                    t.Errorf(&quot;header response doen&#39;t match:\n%s&quot;, p)
            }
    }
}

Server test (which is what you need to use):

func TestIt(t *testing.T){
    ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set(&quot;Content-Type&quot;, &quot;application/json&quot;)
        fmt.Fprintln(w, `{&quot;fake twitter json string&quot;}`)
    }))
    defer ts.Close()

    twitterUrl = ts.URL
    c := make(chan *twitterResult)
    go retrieveTweets(c)
    
    tweet := &lt;-c
    if tweet != expected1 {
        t.Fail()
    }
    tweet = &lt;-c
    if tweet != expected2 {
        t.Fail()
    }
}

BTW, you don't need to pass in the pointer of r, because it's already a pointer.

err = json.Unmarshal(body, r)

EDIT: for my recorder test, I could use my http handler like this:

handler(resp, req)

But my original code is not using the default mux (but from Gorilla/mux), and I have some wrapping around the mux, e.g. insert server logging, and adding request context (Gorilla/context), so I had to start from mux and call ServeHTTP

答案2

得分: 9

最初这段代码片段是在GitHub Gist上找到的,但在尝试将这个概念应用到我的一个项目时,我意识到我必须对主要代码进行重大修改,所以我决定使用docker和curl进行集成测试来测试这些调用。

英文:

Originally this code snippet was found on GitHub Gist, but while trying to apply the concept to one of my projects I realized that I had to modify the main code significantly that I decided to test these calls using an integration test by using docker and curl.

答案3

得分: 8

如果你想测试你的程序,最好是在编写时考虑到测试。例如,如果你将retrieveTweets函数的内部循环提取出来,写成像这样的函数:

func downloadTweets(tweetsUrl string) (*twitterResult, error)

你可以使用httptest包设置的测试服务器的URL来调用它,而不必担心休眠或重复请求。

英文:

If you want to test your program, it is often best to write it with testing in mind. For instance, if you extracted the inner loop of your retrieveTweets function into something like this:

func downloadTweets(tweetsUrl string) (*twitterResult, error)

You could invoke it with the URL of a test server you've set up using the httptest package without having to worry about the sleeps or repeated requests.

huangapple
  • 本文由 发表于 2013年4月23日 03:18:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/16154999.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定