A very popular question people ask when working with Go and HTTP clients is how to test the code with `go test`. You will see many mentions of [interfaces](https://go.dev/tour/methods/9), [test doubles](https://microsoft.github.io/code-with-engineering-playbook/automated-testing/unit-testing/mocking/) (stubs, mocks, fakes, etc.), [httptest](https://pkg.go.dev/net/http/httptest), and so on. I had a similar problem but it was like this: a function in module A was doing some work on the results returned by a function in module B. In other words, module A was depending on module B. Module B is expected to have its own tests, which must not be repeated in the tests written for module A; why duplicate the effort? How could I test module A without also testing module B in the tests written for module A? Let's clarify with some example code, // module: "github.com/example/modulea" // file: main.go package main import ( "fmt" "github.com/example/moduleb" ) func main() { funcA() } func funcA() { result := moduleb.funcB() fmt.Println(result) } How could we rewrite the code so it can be tested in both _moduleb_ (someone else's responsibility) and _modulea_ (our responsibility)? Let's see the code before we discuss it in detail. // module: "github.com/example/moduleb" // file: b.go package moduleb import ( "fmt" ) type Data struct { foo string } type Result string func (d Data) DoWork() Result { return fmt.Sprintf("Did some work in B on data %v", d.foo) } // module: "github.com/example/modulea" // file: main.go package main import ( "fmt" "github.com/example/moduleb" ) type Action interface { DoWork() moduleb.Result } func main() { d := moduleb.Data{ foo: "PRODUCTION DATA", } funcA(d) } func funcA(act Action) { result := act.DoWork() fmt.Println(result) fmt.Println("Did some more work in A") } // module: "github.com/example/modulea" // file: main_test.go package main import ( "fmt" "testing" "github.com/example/moduleb" ) type FakeWork struct { moduleb.Data } func (fw FakeWork) DoWork() moduleb.Result { return fmt.Sprintf("Did some fake work in testing on data %v", fw.foo) } func TestMain(t *testing.T) { fakew := FakeWork{ foo: "TEST DATA", } result := funcA(fakew) t.Log(result) } What happens when you run the code versus the tests? $ go run . Did some work in B on data PRODUCTION DATA Did some more work in A $ go test -v . === RUN TestMain Did some fake work in testing on data TEST DATA Did some more work in A --- PASS: TestMain (0.00s) PASS ok github.com/example/modulea 0.185s You can see the production code path is using production data and test is using the test code path. ## Nested (embedded) struct The main ingredient of this code is nested (embedded) struct. The base struct is _Data{}_ from _moduleb_. It has a method _DoWork()_ which does some work and returns results. This method is expected to already be tested by the maintainers of _moduleb_. The dilemma is that we need to call this method from the production code path and we need to fake it for our tests. We use the natural properties of structs and embedded structs in Go. When a struct (e.g. _L_) is embedded in another struct (e.g. _H_), the fields and methods of _L_ become the fields and methods of _H_. In production code path we use the method of _moduleb_, _DoWork()_, as-is. In the testing code we override the _DoWork()_ method of _moduleb_ with our own, thanks to the Go language properties. Go allows a struct with an embedded struct to override any method of the embedded struct. In our example above, if we define a method of _H_ with the same signature as the method of _L_, when we call _H.Method()_, it calls the version of _Method()_ overridden by _H_. We use this behavior to define a fake version of _DoWork()_ in our tests. Now we don't need to run the _DoWork()_ from _moduleb_ which may be doing a lot of work which could be consuming resources like time, memory, network, etc. Instead, we fake that work and respond quickly. ## Interface The second ingredient we use is interface. _DoWork()_ from _moduleb_ is a method of a particular type. _DoWork()_ in _main\_test.go_ is a method of another type. If we tried to pass both to _funcA()_, it wouldn't work. Go's answer to this problem is an interface. Since both types implement a method with the same signature, we can make them related to each other through an interface. Consequently, the receiving function (_funcA()_) must expect an interface instead of a struct or something else. This is known as the [_accept interfaces, return structs_](https://medium.com/@cep21/preemptive-interface-anti-pattern-in-go-54c18ac0668a) rule of thumb. We define an interface, _Action_, in _package main_ and use it in production code path as well as in tests. Pass a variable of the type that implements an interface to the function that accepts the interface. In _main()_ above we pass _d := Data{}_ to _funcA()_, where _d_ is a variable of type _Data{}_ which implements _Action_ interface. In _TestMain()_ we pass _fakew := FakeWork{}_ to _funcA()_, where _fakew_ is a variable of type _FakeWork{}_ which is not the same as type _Data{}_ but implements the same interface (_Action_) as _Data{}_. In both cases since _funcA()_ accepts _Action_ interface, variables of types _Data{}_ and _FakeWork{}_ can be passed to it. _funcA()_ will run the _DoWork()_ method of the type it was passed. ## Method The third ingredient to make this work was using a [_method_](https://go.dev/tour/methods/1) instead of a [_function_](https://go.dev/tour/basics/4). A function can take multiple arguments, one of which could be a special type. A method is another variation where the special type is the _receiver_ argument instead. By switching to methods, we get the benefits of the first two ingredients discussed above. Use methods more often because they help to write re-usable and fakeable code. ## No Mock Notice there is no mock library in use. We did not need it for this case. Instead, we used fakes. ## Mix Multiple Types in the Same Interface? Yes, we can mix multiple types in the same interface. package main import "fmt" type one struct{} func (o one) Do() { fmt.Println("ONE Do") } type two struct{} func (t two) Make() { fmt.Println("TWO Make") } type Common interface { Do() Make() } type Fake struct { one two } func (f Fake) Do() { fmt.Println("FAKE Do") } func (f Fake) Make() { fmt.Println("FAKE Make") } type Prod struct { one two } func main() { f := new(Fake) f.Do() f.Make() o := new(one) o.Do() t := new(two) t.Make() tmp(f) p := new(Prod) tmp(p) } func tmp(c Common) { fmt.Println("Running func") c.Do() c.Make() } _Prod_ mixes two types in the same struct, each of which has its own method. Methods of both structs are in the same interface. _Fake_ does the same. Additionally, it overrides the methods of both structs. ## Conclusion Understanding interfaces can be a little confusing for newcomers. It was for me. This example gave me a good understanding of the concept. The key to unlocking the solution to this problem was learning more about the combination of embedded structs and interfaces. When you build a module that is meant to be re-used, use structs, custom types, and methods instead of simple functions. It will allow users to create their own fakes. In some cases you may also want to ship some recommended fakes in the module. ## Further Reading - ["Accept interfaces, return structs" in Go](https://bryanftan.medium.com/accept-interfaces-return-structs-in-go-d4cab29a301b) - [mocking outbound http requests in go: you're (probably) doing it wrong](https://medium.com/zus-health/mocking-outbound-http-requests-in-go-youre-probably-doing-it-wrong-60373a38d2aa) - [Mocking in Unit Tests](https://microsoft.github.io/code-with-engineering-playbook/automated-testing/unit-testing/mocking/)