Golang的模拟强制改变函数定义。

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

Golang mock forces to change functions definition

问题

我有以下函数:

  1. func getPrice(date string) {
  2. url := (fmt.Printf("http://endponint/%s", date))
  3. resp, err := http.Get(url)
  4. // 解析响应体并获取价格
  5. return price
  6. }

为了对这个函数进行单元测试,我被迫进行重构:

  1. func getPrice(client HTTPClient, date string) {
  2. url := (fmt.Printf("http://endponint/%s", date))
  3. resp, err := client.Get(url)
  4. // 解析响应体并获取价格
  5. return price
  6. }

我的测试代码如下:

  1. type MockClient struct {
  2. Response *http.Response
  3. Err error
  4. }
  5. func (m *MockClient) Get(url string) (*http.Response, error) {
  6. return m.Response, m.Err
  7. }
  8. mockResp := &http.Response{
  9. StatusCode: http.StatusOK,
  10. Body: ioutil.NopCloser(strings.NewReader("mock response")),
  11. }
  12. client := &MockClient{
  13. Response: mockResp,
  14. Err: nil,
  15. }
  16. data, err := getData(client, "http://example.com")

这是在Go语言中测试的唯一方法吗?没有办法模拟未注入函数的API吗?

英文:

I have the following function:

  1. func getPrice(date string) {
  2. url := (fmt.Printf("http://endponint/%s", date))
  3. resp, err := http.Get(url)
  4. // Unmarshall body and get price
  5. return price
  6. }

In order to unit test this function I'm forced to refactor to:

  1. func getPrice(client HTTPClient, date string) {
  2. url := (fmt.Printf("http://endponint/%s", date))
  3. resp, err := client.Get(url)
  4. // Unmarshall body and get price
  5. return price
  6. }

And my test looks like this:

  1. type MockClient struct {
  2. Response *http.Response
  3. Err error
  4. }
  5. func (m *MockClient) Get(url string) (*http.Response, error) {
  6. return m.Response, m.Err
  7. }
  8. mockResp := &http.Response{
  9. StatusCode: http.StatusOK,
  10. Body: ioutil.NopCloser(strings.NewReader("mock response")),
  11. }
  12. client := &MockClient{
  13. Response: mockResp,
  14. Err: nil,
  15. }
  16. 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可注入:

  1. var APIEndpoint = "http://endpoint/"
  2. func getPrice(date string) (int, error) {
  3. url := (fmt.Printf("%s/%s", APIEndpoint, date))
  4. resp, err := http.Get(url)
  5. // 反序列化响应体并获取价格
  6. return price, nil
  7. }

然后在每个测试中:

  1. srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  2. // 将预期的响应写入writer
  3. }))
  4. defer srv.Close()
  5. APIEndpoint = srv.URL
  6. price, err := getPrice("1/1/2023")
  7. // 处理错误,检查返回值

稍微更好的设计是将你的API封装在一个客户端struct中,并将getPrice作为接收器方法:

  1. type PriceClient struct {
  2. Endpoint string
  3. }
  4. func (pc *PriceClient) GetPrice(date string) (int, error) {
  5. url := (fmt.Printf("%s/%s", pc.Endpoint, date))
  6. resp, err := http.Get(url)
  7. // 反序列化响应体并获取价格
  8. return price, nil
  9. }
  1. srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  2. // 将预期的响应写入writer
  3. }))
  4. defer srv.Close()
  5. c := &PriceClient{Endpoint: srv.URL}
  6. price, err := c.GetPrice("1/1/2023")
  7. // 处理错误,检查返回值

以供参考,你还应该查看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:

  1. var APIEndpoint = "http://endpoint/"
  2. func getPrice(date string) (int, error) {
  3. url := (fmt.Printf("%s/%s", APIEndpoint, date))
  4. resp, err := http.Get(url)
  5. // Unmarshall body and get price
  6. return price, nil
  7. }

And then in each test:

  1. srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  2. // Write the expected response to the writer
  3. }))
  4. defer srv.Close()
  5. APIEndpoint = srv.URL
  6. price, err := getPrice("1/1/2023")
  7. // Handle error, check return

A slightly better design would be to wrap your API in a client struct and make getPrice a receiver method:

  1. type PriceClient struct {
  2. Endpoint string
  3. }
  4. func (pc *PriceClient) GetPrice(date string) (int, error) {
  5. url := (fmt.Printf("%s/%s", pc.Endpoint, date))
  6. resp, err := http.Get(url)
  7. // Unmarshall body and get price
  8. return price, nil
  9. }
  1. srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  2. // Write the expected response to the writer
  3. }))
  4. defer srv.Close()
  5. c := &PriceClient{Endpoint: srv.URL}
  6. price, err := c.GetPrice("1/1/2023")
  7. // 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,并将其设置为你的函数。

  1. package server
  2. var getPrice = getPriceFn
  3. func getPriceFn(date string) {
  4. url := fmt.Printf("http://endponint/%s", date)
  5. resp, err := http.Get(url)
  6. // 解析响应体并获取价格
  7. return price
  8. }

在你的测试文件中给getPrice变量赋予一个模拟函数。

  1. package server
  2. func mockGetPrice(date string) (int, error) {
  3. return 1, nil
  4. }
  5. func testSomething(t *testing.T) {
  6. getPrice = mockGetPrice
  7. // 做一些操作
  8. }

如果你想模拟http.Get以便测试getPrice函数,你可以使用httpmock包。

  1. import "github.com/jarcoal/httpmock"
  2. func TestGetPrice(date string) {
  3. httpmock.Activate()
  4. defer httpmock.DeactivateAndReset()
  5. url := fmt.Printf("http://endponint/%s", date)
  6. httpmock.RegisterResponder("GET", url,
  7. httpmock.NewStringResponder(200, "foo"))
  8. getPrice(date)
  9. // 进行断言
  10. }
英文:

you can mock getPrice like so:

in your package define a variable getPrice and set it to your function.

  1. package server
  2. var getPrice = getPriceFn
  3. func getPriceFn(date string) {
  4. url := (fmt.Printf("http://endponint/%s", date))
  5. resp, err := http.Get(url)
  6. // Unmarshall body and get price
  7. return price
  8. }

in your test file give the getPrice variable a mock function.

  1. package server
  2. func mockGetPrice(date string) (int, error) {
  3. return 1,nil
  4. }
  5. func testSomething(t *testing.T) {
  6. getPrice = mockGetPrice
  7. // do stuff
  8. }

if you want to mock http.get so you can test getPrice, then you can use the httpmock package.

  1. import "github.com/jarcoal/httpmock"
  2. func TestGetPrice(date string) {
  3. httpmock.Activate()
  4. defer httpmock.DeactivateAndReset()
  5. url := (fmt.Printf("http://endponint/%s", date))
  6. httpmock.RegisterResponder("GET", url,
  7. httpmock.NewStringResponder(200, "foo"))
  8. getPrice(date)
  9. // make assertions
  10. }
  11. </details>

huangapple
  • 本文由 发表于 2023年5月19日 21:53:32
  • 转载请务必保留本文链接:https://go.coder-hub.com/76289762.html
匿名

发表评论

匿名网友

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

确定