如何为自定义的HTTP客户端设置接口?

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

How to set an interface for a custom HTTP client?

问题

我正在为我导入的这个第三方库编写测试时遇到了困难。我认为问题出在我想让我的CustomClient结构体拥有一个client接口,而不是*banker.Client。这使得测试变得非常困难,因为很难模拟一个*banker.Client。你有什么办法可以正确地将其转换为接口吗?这样我就可以轻松地编写针对它的模拟测试并设置一个假的客户端了。

type CustomClient struct {
    client     *banker.Client //我想将其改为一个接口
    name  string
    address string
}

func (c *CustomClient) SetHttpClient(httpClient *banker.Client) { //我想接受一个接口,这样我就可以轻松地模拟它。
    c.client = httpClient
}

问题在于banker.Client是一个我导入的第三方客户端,它包含许多结构体和其他字段。它的结构如下:

type Client struct {
    *restclient.Client
    Monitor    *Monitors
    Pricing    *Pricing
    Verifications *Verifications
}

最终,我的代码看起来像这样:

func (c *CustomClient) RequestMoney() {
    _, err := v.client.Verifications.GetMoney("fakeIDhere")
}
英文:

I am having difficulty writing tests for this 3rd party library I am importing. I think this is because I want my CustomClient struct to have a client interface instead of the *banker.Client. This is making testing very difficult because it's hard to mock a *banker.Client. Any ideas how I can correctly turn this into an interface? So I can easily write mock tests against it and set up a fake client?

type CustomClient struct {
	client     *banker.Client //I want to change this to an interface
	name  string
	address string
}

func (c *CustomClient) SetHttpClient(httpClient *banker.Client) { //I want to accept an interface so I can easily mock this.
	c.client = httpClient
}

The problem is that banker.Client is a third party client I am importing with many structs and other fields inside of it. It looks like this:

type Client struct {
	*restclient.Client
	Monitor    *Monitors
	Pricing    *Pricing
    Verifications *Verifications
}

The end result is that my code looks like this:

func (c *CustomClient) RequestMoney() {
	_, err := v.client.Verifications.GetMoney("fakeIDhere")

}

答案1

得分: 1

给定在结构体上的字段方法,这肯定不是一个简单的解决方案。然而,我们可以尝试减少当前包中冗长的测试用例。

在你的工作包和银行家之间添加另一个层次(包)。为了解释,简化示例中的代码。

假设你的banker包有以下代码:

type Client struct {
	Verification *Verification
}

type Verification struct{}

func (v Verification) GetMoney(s string) (int, error) {
	...
}

创建另一个导入银行家并定义接口的包,比如bankops包:

type Bank struct {
	BankClient *banker.Client
}

type Manager interface {
	GetMoney(s string) (int, error)
}

func (b *Bank) GetMoney(s string) (int, error) {
	return b.BankClient.Verification.GetMoney(s)
}

> 注意:实际问题(没有接口的测试)仍然存在于bankops包中,但这样更容易测试,因为我们只是转发结果。达到了单元测试的目的。

最后,在当前包中(对我来说,是main包),我们可以这样做:

type CustomClient struct {
	client bankops.Manager
}

func (c *CustomClient) RequestMoney() {
	_, err := c.client.GetMoney("fakeIDhere")
	...
}

func main() {
	client := &CustomClient{
		client: &bankops.Bank{
			BankClient: &banker.Client{
				Verification: &banker.Verification{},
			},
		},
	}

	client.RequestMoney()
}

要查看工作示例,请在Playground中检查。

你可以像在原始代码片段中所做的那样添加settersbuilders pattern,将字段(如BankerClient)设为非导出的。

英文:

Given methods over fields on the struct, it sure wouldn't be a simple solution. However, we can try to minimize the lengthy test cases on the current package.

Add another layer (package) between your working package and banker. Simplifying the code in example to explain.

Let's say your banker package has the following code:

type Client struct {
	Verification *Verification
}

type Verification struct{}

func (v Verification) GetMoney(s string) (int, error) {
	...
}

Create another package that imports the banker and has interface defined, say bankops package:

type Bank struct {
	BankClient *banker.Client
}

type Manager interface {
	GetMoney(s string) (int, error)
}

func (b *Bank) GetMoney(s string) (int, error) {
	return b.BankClient.Verification.GetMoney(s)
}

> Note: The actual issue (test without interface) is still here in bankops package, but this is easier to test as we are only forwarding the result. Serves the purpose of unit tests.

Finally, in the current package (for me, it is main package), we can

type CustomClient struct {
	client bankops.Manager
}

func (c *CustomClient) RequestMoney() {
	_, err := c.client.GetMoney("fakeIDhere")
	...
}

func main() {
	client := &CustomClient{
		client: &bankops.Bank{
			BankClient: &banker.Client{
				Verification: &banker.Verification{},
			},
		},
	}

	client.RequestMoney()
}

For working example, check in Playground.

You may add the setters or builders pattern as you were doing in your original code snippet to make the fields (like BankerClient) unexported.

答案2

得分: 0

我认为直接将其转换为接口是不可能的,因为我们应该使用Client的成员变量。

将其成员变量转换为接口怎么样?
例如,

   for _, test := []struct{}{
      testVerification VerificationInterface
   }{{
      testVerification: v.Client.Verifications
   },{
      testVerification: VerficationMock
   }}{
      // 在这里编写测试代码
   }
英文:

I think it is impossible to make it into interface directly
because we should use the member variables of the Client.

How about making its member into interface?
For example,

   for _, test := []struct{}{
      testVerification VerificationInterface
   }{{
      testVerification: v.Client.Verifications
   },{
      testVerification: VerficationMock
   }}{
      // test code here
   }

huangapple
  • 本文由 发表于 2022年11月5日 07:46:41
  • 转载请务必保留本文链接:https://go.coder-hub.com/74324023.html
匿名

发表评论

匿名网友

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

确定