Home > Mobile >  How to set an interface for a custom HTTP client?
How to set an interface for a custom HTTP client?

Time:11-05

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")

}

CodePudding user response:

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
   }

CodePudding user response:

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.

  • Related