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

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

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>



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:

确定