So testen Sie http-Aufrufe in Go mit httptest

76

Ich habe folgenden Code:

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)
    }

}

Ich möchte einige Tests für sie schreiben, aber ich bin nicht sicher , wie das httptest Paket verwenden http://golang.org/pkg/net/http/httptest/ würde einige Hinweise zu schätzen wissen

Ich habe mir das ausgedacht (schamlos kopiert von den Tests für 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)
    }
}
Jwesonga
quelle

Antworten:

91

httptest führt zwei Arten von Tests durch: Antwort und Server

Reaktionstest:

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 doen't match:\n%s", p)
            }
    }
}

Servertest (den Sie verwenden müssen):

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()
    }
}

Übrigens müssen Sie den Zeiger von r nicht übergeben, da er bereits ein Zeiger ist.

err = json.Unmarshal(body, r)

EDIT: Für meinen Rekorder-Test könnte ich meinen http-Handler folgendermaßen verwenden:

handler(resp, req)

Aber mein ursprünglicher Code verwendet nicht den Standard-Mux (sondern von Gorilla / Mux), und ich habe einige Umgehungen um den Mux, z. B. Serverprotokollierung einfügen und Anforderungskontext (Gorilla / Kontext) hinzufügen, also musste ich von Mux und starten Rufen Sie ServeHTTP auf

nemo
quelle
12
Bitte fügen Sie vollständigen Code hinzu, z. B. den Import aller Pakete. Neuling hier;) Danke!
Marçal Juan
8
Sie könnten goimports verwenden
030
9

Ursprünglich wurde dieses Code-Snippet auf GitHub Gist gefunden , aber als ich versuchte, das Konzept auf eines meiner Projekte anzuwenden, stellte ich fest, dass ich den Hauptcode erheblich ändern musste, sodass ich mich entschied, diese Aufrufe mithilfe eines Integrationstests mithilfe von Docker und Curl zu testen.

030
quelle
8

Wenn Sie Ihr Programm testen möchten, ist es oft am besten, es zu Testzwecken zu schreiben. Wenn Sie beispielsweise die innere Schleife Ihrer retrieveTweetsFunktion in Folgendes extrahiert haben :

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

Sie können es mit der URL eines Testservers aufrufen, den Sie mit dem httptestPaket eingerichtet haben, ohne sich um den Ruhezustand oder wiederholte Anforderungen kümmern zu müssen.

James Henstridge
quelle
Kann ich einen Server verspotten? Ich denke, das Benetzen eines Testservers negiert den Zweck des Tests, wenn ich einen Server und die erwartete Antwort, die gut zum Test passt, verspotten kann
jwesonga
4
Das httptestPaket ist die Infrastruktur zum Einrichten eines kleinen HTTP-Servers für die Tests. Sie können die Anforderungshandler wie gewohnt implementieren und dann Ihren Code auf diesem Server anstatt auf Twitter ausführen.
James Henstridge
@ JamesHenstridge danke, tolle Antwort! Ihr Kommentar hier ist informativer als die Übersichtserklärung zu den offiziellen Dokumenten für net / http / httptest
Rob Kielty