如何使用Go的httptest模拟多个不同的HTTP响应?

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

How to simulate multiple different HTTP responses using Go's httptest?

问题

我已经创建了一些Go函数,用于向互联网上的服务发出HTTP GET请求并解析结果。

我现在正在编写这些函数的测试用例。
在我的测试用例中,我使用go包httptest来模拟对这些外部服务的调用。以下是我的代码。出于简洁起见,我故意删除了错误检查。这是go-playground

package main

import (
    "fmt"
    "io"
    "context"
    "net/http"
    "net/http/httptest"
)

func handlerResponse() http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusOK)
        w.Write([]byte(`{"A":"B"}`))
    })
}

func buildMyRequest(ctx context.Context, url string) *http.Request {
    request, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
    return request
}

func myPrint(response *http.Response) {
    b := make([]byte, 60000)
    for {
        _, err := response.Body.Read(b)
        if err == io.EOF {
            break
        }
    }
    fmt.Println(string(b))
}

func main() {
    srv := httptest.NewServer(handlerResponse())			
    client := http.Client{}

    myResponse1, _ := client.Do(buildMyRequest(context.Background(), srv.URL))
    fmt.Println("myResponse1:")
    myPrint(myResponse1)
    
    myResponse2, _ := client.Do(buildMyRequest(context.Background(), srv.URL))
    fmt.Println("myResponse2:")
    myPrint(myResponse2)

}

它产生的输出如下:

myResponse1:
{"A":"B"}
myResponse2:
{"A":"B"}

如你所见,我创建了一些虚拟的HTTP响应数据{"A":"B"},当你向srv.URL发送HTTP请求时,它实际上会命中一个临时的HTTP服务器,该服务器会响应虚拟数据。很酷吧!

当你向srv.URL发送第二个HTTP请求时,它再次响应相同的虚拟数据。但这就是我的问题所在。我希望临时的HTTP服务器在接收到请求时第二次返回一些不同的数据{"C":"D"},第三次返回{"E":"F"}

我该如何修改main()函数的第一行,以便服务器在后续的HTTP调用中返回我期望的数据?

英文:

I have created some Go functions that make HTTP GET calls to services that are out there on the internet and parse the results.

I am now working on writing test-cases for these functions.
In my test cases, I'm using the go package httptest to simulate calls to these external services. Below is my code. Error checking is purposefully removed for brevity. Here is the go-playground.

package main

import (
    "fmt"
    "io"
    "context"
    "net/http"
    "net/http/httptest"
)

func handlerResponse() http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusOK)
        w.Write([]byte(`{"A":"B"}`))
    })
}

func buildMyRequest(ctx context.Context, url string) *http.Request {
    request, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
    return request
}

func myPrint(response *http.Response) {
    b := make([]byte, 60000)
    for {
        _, err := response.Body.Read(b)
        if err == io.EOF {
            break
        }
    }
    fmt.Println(string(b))
}

func main() {
    srv := httptest.NewServer(handlerResponse())			
    client := http.Client{}

    myResponse1, _ := client.Do(buildMyRequest(context.Background(), srv.URL))
    fmt.Println("myResponse1:")
    myPrint(myResponse1)
    
    myResponse2, _ := client.Do(buildMyRequest(context.Background(), srv.URL))
    fmt.Println("myResponse2:")
    myPrint(myResponse2)

}

This is the output it produces:

myResponse1:
{"A":"B"}
myResponse2:
{"A":"B"}

As you can see, I have created some dummy HTTP response data {"A":"B"} and when you send an HTTP request to srv.URL, it actually hits an ephemeral HTTP server which responds with the dummy data. Cool!

When you send the second HTTP request to srv.URL, it again responds with the same dummy data. But this is where my problem arises. I want the ephemeral HTTP server to return some different data the second time {"C":"D"} and third time {"E":"F"} it receives a request.

How can I change the first line of the main() function so that the server responds with my desired data on subsequent HTTP calls?

答案1

得分: 1

你可以使用以下的方法来实现(在playground上查看:这里

package main

import (
	"fmt"
	"io"
	"context"
	"net/http"
	"net/http/httptest"
	"sync"
)


type responseWriter struct{
   resp map[int]string
   count int
   lock *sync.Mutex
}

func NewResponseWriter()*responseWriter{
   r := new(responseWriter)
   r.lock = new(sync.Mutex)
   r.resp = map[int]string{
	0: `{"E":"F"}`,
	1: `{"A":"B"}`,
	2: `{"C":"D"}`,
   }
   r.count = 0
   return r
}

func (r *responseWriter)GetResp()string{
   r.lock.Lock()
   defer r.lock.Unlock()
   r.count ++
   return r.resp[r.count%3]
}


func handlerResponse(rr *responseWriter) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Write([]byte(rr.GetResp()))
	})
}

func buildMyRequest(ctx context.Context, url string) *http.Request {
    request, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
    return request
}


func myPrint(response *http.Response) {
	b := make([]byte, 60000)
	for {
		_, err := response.Body.Read(b)
		if err == io.EOF {
			break
		}
	}
	fmt.Println(string(b))
}

func main() {
        rr := NewResponseWriter()

	srv := httptest.NewServer(handlerResponse(rr))	
	client := http.Client{}

	myResponse1, err := client.Do(buildMyRequest(context.Background(), srv.URL))
	if err != nil{
	   fmt.Println(err)
	   return
	}
	
	defer myResponse1.Body.Close()
	fmt.Println("myResponse1:")
	myPrint(myResponse1)
	
	myResponse2, err := client.Do(buildMyRequest(context.Background(), srv.URL))
	if err != nil{
	   fmt.Println(err)
	   return
	}
	
	defer myResponse2.Body.Close()
	fmt.Println("myResponse2:")
	myPrint(myResponse2)
}
英文:

you could use a hack like follows ( playground : here)

package main
import (
"fmt"
"io"
"context"
"net/http"
"net/http/httptest"
"sync"
)
type responseWriter struct{
resp map[int]string
count int
lock *sync.Mutex
}
func NewResponseWriter()*responseWriter{
r := new(responseWriter)
r.lock = new(sync.Mutex)
r.resp = map[int]string{
0: `{"E":"F"}`,
1: `{"A":"B"}`,
2: `{"C":"D"}`,
}
r.count = 0
return r
}
func (r *responseWriter)GetResp()string{
r.lock.Lock()
defer r.lock.Unlock()
r.count ++
return r.resp[r.count%3]
}
func handlerResponse(rr *responseWriter) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(rr.GetResp()))
})
}
func buildMyRequest(ctx context.Context, url string) *http.Request {
request, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
return request
}
func myPrint(response *http.Response) {
b := make([]byte, 60000)
for {
_, err := response.Body.Read(b)
if err == io.EOF {
break
}
}
fmt.Println(string(b))
}
func main() {
rr := NewResponseWriter()
srv := httptest.NewServer(handlerResponse(rr))	
client := http.Client{}
myResponse1, err := client.Do(buildMyRequest(context.Background(), srv.URL))
if err != nil{
fmt.Println(err)
return
}
defer myResponse1.Body.Close()
fmt.Println("myResponse1:")
myPrint(myResponse1)
myResponse2, err := client.Do(buildMyRequest(context.Background(), srv.URL))
if err != nil{
fmt.Println(err)
return
}
defer myResponse2.Body.Close()
fmt.Println("myResponse2:")
myPrint(myResponse2)
}

huangapple
  • 本文由 发表于 2021年8月20日 13:46:06
  • 转载请务必保留本文链接:https://go.coder-hub.com/68857488.html
匿名

发表评论

匿名网友

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

确定