28

Let me preface this by saying I'm pretty new to Go, so I am looking for mocking techniques when working with other libraries. I am well aware that interfaces and dependency injection are the best way to keep code testable and mockable.

While working with a 3rd party client library (Google Cloud Storage), I have run into a problem with attempting to mock the implementation of their client. The primary problem is that the types in the client library are not implemented with interfaces. I can generate interfaces to mimic the client implementation. However, the return values for some of the functions return pointers to underlying struct types which are tricky or impossible to mock due to private attributes. Here is a sample of the problem I am trying to solve:

package third_party

type UnderlyingType struct {
    secret string
}

type ThirdPartyClient struct {}
func (f *ThirdPartyClient) SomeFunction() *UnderlyingType {
    return &UnderlyingType{
          secret: "I can't mock this, it's a secret to the package"
    }
}

Here is an annotated sample with the problem I'm trying to solve.

package mock

// Create interface that matches third party client structure
type MyClientInterface interface {
    SomeFunction() *third_party.UnderlyingType
}

type MockClient struct {
    third_party.Client
}
// Forced to return the third party non-interface type 'UnderlyingType'
func (f *MockClient) SomeFunction() *UnderlyingType { 

    // No way to mock the value of the 'secret' property outside
    // of the third-party package. Any underlying methods that 
    // depend on a non-nil reference to 'secret' will explode 
    // with the mock.
    //
    // TODO: Find a way to mock the 'secret' value
    return &UnderlyingType{}
}

Is this even a mockable scenario? Are there special techniques to work around the fact that the library provides no interfaces as return types?

Roosh
  • 651
  • 1
  • 8
  • 16
  • 1
    What can you do with `UnderlyingType`? Does it have exported fields or methods? Or is it just something you pass to other functions in the package? If `secret` is internal to the package, it doesn't seem like you need to mock it for testing. – Andy Schweig Mar 09 '17 at 17:05
  • In my case, the UnderlyingType is an implementation of io.Reader. The 'secret' of UnderlyingType is a reference to the client. Since I can't assign "secret" a mock client instance, I can't perform mock operations or assertions on the client reference of UnderlyingType. – Roosh Mar 09 '17 at 18:51
  • To answer your question on exported methods, yes. The exported methods try to reference the private member. Since my mock client is composed from the actual implementation, the exported functions of the mock will end up referencing nil since I am unable to set the private member. – Roosh Mar 09 '17 at 19:09
  • Wouldn't returning a "mock" of the `UnderlyintType` solve the issue? If it's an `io.Reader`, and it's all you need for your app to work, then you might wanna return an `io.Reader` instead of the `UnderlyingType`. – mkopriva Mar 09 '17 at 19:45
  • @mkopriva That's the problem. I have no control of the implementation of UnderlyingType. It's a third party library. To my knowledge, the best I can do is create an interface that matches the third party's implementation, which forces me to have the interface enforce the same return type. – Roosh Mar 09 '17 at 19:55
  • 4
    If [this](https://godoc.org/cloud.google.com/go/storage#Client) is what you're using then you might wanna consider building an abstraction on top of it and treat the `storage.Client` as an implementation detail, sort of like you would build repositories on top of Go's `database/sql` package... e.g. if all you want is to store and retrieve files, then create you're own type `Store` that handles `io.Reader`s/`io.Writer`s and whose implementation uses google storage, but then in your tests you can just mock your `Store` type instead of the whole `cloud.google.com/go/storage` package. – mkopriva Mar 09 '17 at 20:16

2 Answers2

12

In general, one approach you can take when dealing with non-test-friendly third party libraries is to abstract the third party code away with an intermediate layer.

// mock and use this interface
type IntermediateLayer interface {
    DoSomething()
}

type intermediateImplementation struct{}

func (i intermediateImplementation) DoSomething() {
    client := &ThirdPartyClient{}
    underlyingValue := client.SomeFunction()
    underlyingValue.SomeOtherFunction()
}

You can mock the IntermediateLayer interface and test the business code that uses it. You will need to create a structure that implements the IntermediateLayer interface and uses the third party API to achieve your goal.

Then, the problem would be shifted to testing the IntermediateLayer. Depending on how complex the code that uses the third party library is, you can either opt to not test it or leave it to higher level tests (like integration tests) to verify it.

One benefit of going down this road is that you are decoupling your business code from the third party library, which allows you to switch to a different third party library at some point in the future without having to rework all of your code. You may even consider using this approach even when dealing with test-friendly third party libraries, at the cost of more abstractions and boilerplate code.

Momchil Atanasov
  • 479
  • 4
  • 10
2

The answer to your question is: yes this is the way you could do it.

But you asked the wrong question. You should not aks how you could mock something. Because, when do you need a mock?

Just for testing. So you should make a specific example what you want to test.

When you use external packages you have 2 possibilities. You want to test, if the external package behaves like you expect, or you trust that external package and you are just testing your code.

So when you test your code you need to test, if the client is called correct. So your mock is ok for that case. Just keep in mind it is important, what you are testing and not if you could mock something.

apxp
  • 5,240
  • 4
  • 23
  • 43
  • In his case he wants to write a unit test (=testing his code), but is not allowed due to the library. How do we proceed in this case? We wrap an interface around it? – ianaz Mar 11 '19 at 11:11
  • 1
    I understand it. But the question is not about mocking it is about testing. And when you test you should asume that the 3rd library is working correct. How does the function look like, which you want to test? – apxp Mar 11 '19 at 13:21
  • I just asked the question here if you want to follow :) https://stackoverflow.com/questions/55105509/golang-interfaces-mocking – ianaz Mar 11 '19 at 15:42