英文:
Golang mock forces to change functions definition
问题
我有以下函数:
func getPrice(date string) {
url := (fmt.Printf("http://endponint/%s", date))
resp, err := http.Get(url)
// 解析响应体并获取价格
return price
}
为了对这个函数进行单元测试,我被迫进行重构:
func getPrice(client HTTPClient, date string) {
url := (fmt.Printf("http://endponint/%s", date))
resp, err := client.Get(url)
// 解析响应体并获取价格
return price
}
我的测试代码如下:
type MockClient struct {
Response *http.Response
Err error
}
func (m *MockClient) Get(url string) (*http.Response, error) {
return m.Response, m.Err
}
mockResp := &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(strings.NewReader("mock response")),
}
client := &MockClient{
Response: mockResp,
Err: nil,
}
data, err := getData(client, "http://example.com")
这是在Go语言中测试的唯一方法吗?没有办法模拟未注入函数的API吗?
英文:
I have the following function:
func getPrice(date string) {
url := (fmt.Printf("http://endponint/%s", date))
resp, err := http.Get(url)
// Unmarshall body and get price
return price
}
In order to unit test this function I'm forced to refactor to:
func getPrice(client HTTPClient, date string) {
url := (fmt.Printf("http://endponint/%s", date))
resp, err := client.Get(url)
// Unmarshall body and get price
return price
}
And my test looks like this:
type MockClient struct {
Response *http.Response
Err error
}
func (m *MockClient) Get(url string) (*http.Response, error) {
return m.Response, m.Err
}
mockResp := &http.Response{
StatusCode: http.StatusOK,
Body: ioutil.NopCloser(strings.NewReader("mock response")),
}
client := &MockClient{
Response: mockResp,
Err: nil,
}
data, err := getData(client, "http://example.com")
Is this only way to test in Go, is there no way to mock an API which is not injected into function?
答案1
得分: 1
使用Go进行HTTP测试的惯用方法是使用http/httptest(示例)。
在你的情况下,你只需要使基本URL可注入:
var APIEndpoint = "http://endpoint/"
func getPrice(date string) (int, error) {
url := (fmt.Printf("%s/%s", APIEndpoint, date))
resp, err := http.Get(url)
// 反序列化响应体并获取价格
return price, nil
}
然后在每个测试中:
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 将预期的响应写入writer
}))
defer srv.Close()
APIEndpoint = srv.URL
price, err := getPrice("1/1/2023")
// 处理错误,检查返回值
稍微更好的设计是将你的API封装在一个客户端struct
中,并将getPrice
作为接收器方法:
type PriceClient struct {
Endpoint string
}
func (pc *PriceClient) GetPrice(date string) (int, error) {
url := (fmt.Printf("%s/%s", pc.Endpoint, date))
resp, err := http.Get(url)
// 反序列化响应体并获取价格
return price, nil
}
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 将预期的响应写入writer
}))
defer srv.Close()
c := &PriceClient{Endpoint: srv.URL}
price, err := c.GetPrice("1/1/2023")
// 处理错误,检查返回值
以供参考,你还应该查看gomock,因为你将遇到的大多数其他模拟问题在语言中没有内置解决方案。
英文:
The idiomatic way to do HTTP testing with Go is using http/httptest (examples)
In your case, all you would need to do is make the base URL injectable:
var APIEndpoint = "http://endpoint/"
func getPrice(date string) (int, error) {
url := (fmt.Printf("%s/%s", APIEndpoint, date))
resp, err := http.Get(url)
// Unmarshall body and get price
return price, nil
}
And then in each test:
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Write the expected response to the writer
}))
defer srv.Close()
APIEndpoint = srv.URL
price, err := getPrice("1/1/2023")
// Handle error, check return
A slightly better design would be to wrap your API in a client struct
and make getPrice
a receiver method:
type PriceClient struct {
Endpoint string
}
func (pc *PriceClient) GetPrice(date string) (int, error) {
url := (fmt.Printf("%s/%s", pc.Endpoint, date))
resp, err := http.Get(url)
// Unmarshall body and get price
return price, nil
}
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Write the expected response to the writer
}))
defer srv.Close()
c := &PriceClient{Endpoint: srv.URL}
price, err := c.GetPrice("1/1/2023")
// Handle error, check return
For future reference, you should also have a look at gomock, since most other mocking problems you'll encounter do not have solutions built-in to the language.
答案2
得分: 1
你可以这样模拟getPrice
函数:
在你的包中定义一个变量getPrice
,并将其设置为你的函数。
package server
var getPrice = getPriceFn
func getPriceFn(date string) {
url := fmt.Printf("http://endponint/%s", date)
resp, err := http.Get(url)
// 解析响应体并获取价格
return price
}
在你的测试文件中给getPrice
变量赋予一个模拟函数。
package server
func mockGetPrice(date string) (int, error) {
return 1, nil
}
func testSomething(t *testing.T) {
getPrice = mockGetPrice
// 做一些操作
}
如果你想模拟http.Get
以便测试getPrice
函数,你可以使用httpmock
包。
import "github.com/jarcoal/httpmock"
func TestGetPrice(date string) {
httpmock.Activate()
defer httpmock.DeactivateAndReset()
url := fmt.Printf("http://endponint/%s", date)
httpmock.RegisterResponder("GET", url,
httpmock.NewStringResponder(200, "foo"))
getPrice(date)
// 进行断言
}
英文:
you can mock getPrice
like so:
in your package define a variable getPrice
and set it to your function.
package server
var getPrice = getPriceFn
func getPriceFn(date string) {
url := (fmt.Printf("http://endponint/%s", date))
resp, err := http.Get(url)
// Unmarshall body and get price
return price
}
in your test file give the getPrice
variable a mock function.
package server
func mockGetPrice(date string) (int, error) {
return 1,nil
}
func testSomething(t *testing.T) {
getPrice = mockGetPrice
// do stuff
}
if you want to mock http.get
so you can test getPrice, then you can use the httpmock
package.
import "github.com/jarcoal/httpmock"
func TestGetPrice(date string) {
httpmock.Activate()
defer httpmock.DeactivateAndReset()
url := (fmt.Printf("http://endponint/%s", date))
httpmock.RegisterResponder("GET", url,
httpmock.NewStringResponder(200, "foo"))
getPrice(date)
// make assertions
}
</details>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论