英文:
Golang: Intercepting and Mocking an HTTP Response with httptest
问题
我已经研究了各种可以用于在golang中进行模拟测试的工具,但我正在尝试使用httptest来完成这个任务。特别是,我有一个如下所示的函数:
type contact struct {
username string
number int
}
func getResponse(c contact) string {
url := fmt.Sprintf("https://mywebsite/%s", c.username)
req, err := http.NewRequest(http.MethodGet, url, nil)
// 错误检查
resp, err := http.DefaultClient.Do(req)
// 错误检查
return response
}
我阅读的许多文档似乎要求创建一个客户端接口或自定义传输。在不更改主要代码的情况下,是否没有办法在测试文件中模拟响应?我希望将客户端、响应和所有相关细节都保留在getResponse
函数中。我可能有错误的想法,但我正在尝试找到一种拦截http.DefaultClient.Do(req)
调用并返回自定义响应的方法,这种方法可行吗?
英文:
I've looked into various different tools that can be used for mock testing in golang, but I'm trying to accomplish this task using httptest. In particular, I have a function as such:
type contact struct {
username string
number int
}
func getResponse(c contact) string {
url := fmt.Sprintf("https://mywebsite/%s", c.username)
req, err := http.NewRequest(http.MethodGet, url, nil)
// error checking
resp, err := http.DefaultClient.Do(req)
// error checking
return response
}
A lot of the documentation I've read seems to require creating a client interface or a custom transport. Is there no way to mock a response in a test file without changing this main code at all? I want to keep my client, response, and all the related details within the getResponse
function. I could have the wrong idea, but I'm trying to find a way to intercept the http.DefaultClient.Do(req)
call and return a custom response, is that possible?
答案1
得分: 1
我已经阅读了,似乎需要创建一个客户端接口,而不需要改变主要代码。
保持代码整洁是一个好的实践,你最终会习惯的,可测试的代码更加整洁,而整洁的代码更容易进行测试,所以不要担心改变你的代码(使用接口)以便接受模拟对象。
以下是你的代码的简单形式:
package main
import (
"fmt"
"net/http"
)
type contact struct {
username string
number int
}
type Client interface {
Do(req *http.Request) (*http.Response, error)
}
func main() {
getResponse(http.DefaultClient, contact{})
}
func getResponse(client Client, c contact) string {
url := fmt.Sprintf("https://mywebsite/%s", c.username)
req, _ := http.NewRequest(http.MethodGet, url, nil)
// 错误检查
resp, _ := http.DefaultClient.Do(req)
// 错误检查和响应处理
return response
}
你的测试可以是这样的:
package main
import (
"net/http"
"testing"
)
type mockClient struct {
}
// Do 函数将使 mockClient 实现 Client 接口
func (tc mockClient) Do(req *http.Request) (*http.Response, error) {
return &http.Response{}, nil
}
func TestGetResponse(t *testing.T) {
client := new(mockClient)
getResponse(client, contact{})
}
但是如果你更喜欢使用 httptest:
package main
import (
"fmt"
"io"
"net/http"
"net/http/httptest"
)
type contact struct {
username string
number int
}
func main() {
fmt.Println(getResponse(contact{}))
}
func getResponse(c contact) string {
// 创建一个测试服务器
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "your response")
}))
defer ts.Close()
// 你仍然需要设置你的基本 URL
base_url := ts.URL
url := fmt.Sprintf("%s/%s", base_url, c.username)
req, _ := http.NewRequest(http.MethodGet, url, nil)
// 在你的测试中使用 ts.Client() 而不是 http.DefaultClient。
resp, _ := ts.Client().Do(req)
// 处理响应
response, _ := io.ReadAll(resp.Body)
resp.Body.Close()
return string(response)
}
英文:
> I've read seems to require creating a client interface
> without changing this main code at all
Keeping your code clean is a good practice and you'll finally get used to it, a testable code is cleaner and a cleaner code is more testable, so don't worry to change your code (using interfaces) so it can accept mock objects.
Your code in its simplest form can be like this:
package main
import (
"fmt"
"net/http"
)
type contact struct {
username string
number int
}
type Client interface {
Do(req *http.Request) (*http.Response, error)
}
func main() {
getResponse(http.DefaultClient, contact{})
}
func getResponse(client Client, c contact) string {
url := fmt.Sprintf("https://mywebsite/%s", c.username)
req, _ := http.NewRequest(http.MethodGet, url, nil)
// error checking
resp, _ := http.DefaultClient.Do(req)
// error checking and response processing
return response
}
And your test can be like this:
package main
import (
"net/http"
"testing"
)
type mockClient struct {
}
// Do function will cause mockClient to implement the Client interface
func (tc mockClient) Do(req *http.Request) (*http.Response, error) {
return &http.Response{}, nil
}
func TestGetResponse(t *testing.T) {
client := new(mockClient)
getResponse(client, contact{})
}
But if you prefer to use httptest:
package main
import (
"fmt"
"io"
"net/http"
"net/http/httptest"
)
type contact struct {
username string
number int
}
func main() {
fmt.Println(getResponse(contact{}))
}
func getResponse(c contact) string {
// Make a test server
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "your response")
}))
defer ts.Close()
// You should still set your base url
base_url := ts.URL
url := fmt.Sprintf("%s/%s", base_url, c.username)
req, _ := http.NewRequest(http.MethodGet, url, nil)
// Use ts.Client() instead of http.DefaultClient in your tests.
resp, _ := ts.Client().Do(req)
// Processing the response
response, _ := io.ReadAll(resp.Body)
resp.Body.Close()
return string(response)
}
答案2
得分: 0
你可以使用https://pkg.go.dev/net/http/httptest#example-Server
作为一个很好的例子,通过对你的代码进行一些小的重构来满足你的使用情况。
你只需要将getResponse()
更改为getResponse(url string)
,以便能够提供服务器模拟的URL。
英文:
https://pkg.go.dev/net/http/httptest#example-Server is a good example for your use case with a small refactoring of your code.
You just have to change the getResponse() by getResponse(url string) to be able to give the server mock url.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论