Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mocking non-interface types in Go

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?

like image 607
Roosh Avatar asked Mar 09 '17 16:03

Roosh


2 Answers

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.

like image 89
Momchil Atanasov Avatar answered Sep 19 '22 13:09

Momchil Atanasov


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.

like image 42
apxp Avatar answered Sep 18 '22 13:09

apxp