1# Testing Code that depends on Go Client Libraries
2
3The Go client libraries generated as a part of `cloud.google.com/go` all take
4the approach of returning concrete types instead of interfaces. That way, new
5fields and methods can be added to the libraries without breaking users. This
6document will go over some patterns that can be used to test code that depends
7on the Go client libraries.
8
9## Testing gRPC services using fakes
10
11*Note*: You can see the full
12[example code using a fake here](https://github.com/googleapis/google-cloud-go/tree/master/internal/examples/fake).
13
14The clients found in `cloud.google.com/go` are gRPC based, with a couple of
15notable exceptions being the [`storage`](https://pkg.go.dev/cloud.google.com/go/storage)
16and [`bigquery`](https://pkg.go.dev/cloud.google.com/go/bigquery) clients.
17Interactions with gRPC services can be faked by serving up your own in-memory
18server within your test. One benefit of using this approach is that you don’t
19need to define an interface in your runtime code; you can keep using
20concrete struct types. You instead define a fake server in your test code. For
21example, take a look at the following function:
22
23```go
24import (
25        "context"
26        "fmt"
27        "log"
28        "os"
29
30        translate "cloud.google.com/go/translate/apiv3"
31        "github.com/googleapis/gax-go/v2"
32        translatepb "google.golang.org/genproto/googleapis/cloud/translate/v3"
33)
34
35func TranslateTextWithConcreteClient(client *translate.TranslationClient, text string, targetLang string) (string, error) {
36        ctx := context.Background()
37        log.Printf("Translating %q to %q", text, targetLang)
38        req := &translatepb.TranslateTextRequest{
39                Parent:             fmt.Sprintf("projects/%s/locations/global", os.Getenv("GOOGLE_CLOUD_PROJECT")),
40                TargetLanguageCode: "en-US",
41                Contents:           []string{text},
42        }
43        resp, err := client.TranslateText(ctx, req)
44        if err != nil {
45                return "", fmt.Errorf("unable to translate text: %v", err)
46        }
47        translations := resp.GetTranslations()
48        if len(translations) != 1 {
49                return "", fmt.Errorf("expected only one result, got %d", len(translations))
50        }
51        return translations[0].TranslatedText, nil
52}
53```
54
55Here is an example of what a fake server implementation would look like for
56faking the interactions above:
57
58```go
59import (
60        "context"
61
62        translatepb "google.golang.org/genproto/googleapis/cloud/translate/v3"
63)
64
65type fakeTranslationServer struct {
66        translatepb.UnimplementedTranslationServiceServer
67}
68
69func (f *fakeTranslationServer) TranslateText(ctx context.Context, req *translatepb.TranslateTextRequest) (*translatepb.TranslateTextResponse, error) {
70        resp := &translatepb.TranslateTextResponse{
71                Translations: []*translatepb.Translation{
72                        &translatepb.Translation{
73                                TranslatedText: "Hello World",
74                        },
75                },
76        }
77        return resp, nil
78}
79```
80
81All of the generated protobuf code found in [google.golang.org/genproto](https://pkg.go.dev/google.golang.org/genproto)
82contains a similar `package.UnimplmentedFooServer` type that is useful for
83creating fakes. By embedding the unimplemented server in the
84`fakeTranslationServer`, the fake will “inherit” all of the RPCs the server
85exposes. Then, by providing our own `fakeTranslationServer.TranslateText`
86method you can “override” the default unimplemented behavior of the one RPC that
87you would like to be faked.
88
89The test itself does require a little bit of setup: start up a `net.Listener`,
90register the server, and tell the client library to call the server:
91
92```go
93import (
94        "context"
95        "net"
96        "testing"
97
98        translate "cloud.google.com/go/translate/apiv3"
99        "google.golang.org/api/option"
100        translatepb "google.golang.org/genproto/googleapis/cloud/translate/v3"
101        "google.golang.org/grpc"
102)
103
104func TestTranslateTextWithConcreteClient(t *testing.T) {
105        ctx := context.Background()
106
107        // Setup the fake server.
108        fakeTranslationServer := &fakeTranslationServer{}
109        l, err := net.Listen("tcp", "localhost:0")
110        if err != nil {
111                t.Fatal(err)
112        }
113        gsrv := grpc.NewServer()
114        translatepb.RegisterTranslationServiceServer(gsrv, fakeTranslationServer)
115        fakeServerAddr := l.Addr().String()
116        go func() {
117                if err := gsrv.Serve(l); err != nil {
118                        panic(err)
119                }
120        }()
121
122        // Create a client.
123        client, err := translate.NewTranslationClient(ctx,
124                option.WithEndpoint(fakeServerAddr),
125                option.WithoutAuthentication(),
126                option.WithGRPCDialOption(grpc.WithInsecure()),
127        )
128        if err != nil {
129                t.Fatal(err)
130        }
131
132        // Run the test.
133        text, err := TranslateTextWithConcreteClient(client, "Hola Mundo", "en-US")
134        if err != nil {
135                t.Fatal(err)
136        }
137        if text != "Hello World" {
138                t.Fatalf("got %q, want Hello World", text)
139        }
140}
141```
142
143## Testing using mocks
144
145*Note*: You can see the full
146[example code using a mock here](https://github.com/googleapis/google-cloud-go/tree/master/internal/examples/mock).
147
148When mocking code you need to work with interfaces. Let’s create an interface
149for the `cloud.google.com/go/translate/apiv3` client used in the
150`TranslateTextWithConcreteClient` function mentioned in the previous section.
151The `translate.Client` has over a dozen methods but this code only uses one of
152them. Here is an interface that satisfies the interactions of the
153`translate.Client` in this function.
154
155```go
156type TranslationClient interface {
157        TranslateText(ctx context.Context, req *translatepb.TranslateTextRequest, opts ...gax.CallOption) (*translatepb.TranslateTextResponse, error)
158}
159```
160
161Now that we have an interface that satisfies the method being used we can
162rewrite the function signature to take the interface instead of the concrete
163type.
164
165```go
166func TranslateTextWithInterfaceClient(client TranslationClient, text string, targetLang string) (string, error) {
167// ...
168}
169```
170
171This allows a real `translate.Client` to be passed to the method in production
172and for a mock implementation to be passed in during testing. This pattern can
173be applied to any Go code, not just `cloud.google.com/go`. This is because
174interfaces in Go are implicitly satisfied. Structs in the client libraries can
175implicitly implement interfaces defined in your codebase. Let’s take a look at
176what it might look like to define a lightweight mock for the `TranslationClient`
177interface.
178
179```go
180import (
181        "context"
182        "testing"
183
184        "github.com/googleapis/gax-go/v2"
185        translatepb "google.golang.org/genproto/googleapis/cloud/translate/v3"
186)
187
188type mockClient struct{}
189
190func (*mockClient) TranslateText(_ context.Context, req *translatepb.TranslateTextRequest, opts ...gax.CallOption) (*translatepb.TranslateTextResponse, error) {
191        resp := &translatepb.TranslateTextResponse{
192                Translations: []*translatepb.Translation{
193                        &translatepb.Translation{
194                                TranslatedText: "Hello World",
195                        },
196                },
197        }
198        return resp, nil
199}
200
201func TestTranslateTextWithAbstractClient(t *testing.T) {
202        client := &mockClient{}
203        text, err := TranslateTextWithInterfaceClient(client, "Hola Mundo", "en-US")
204        if err != nil {
205                t.Fatal(err)
206        }
207        if text != "Hello World" {
208                t.Fatalf("got %q, want Hello World", text)
209        }
210}
211```
212
213If you prefer to not write your own mocks there are mocking frameworks such as
214[golang/mock](https://github.com/golang/mock) which can generate mocks for you
215from an interface. As a word of caution though, try to not
216[overuse mocks](https://testing.googleblog.com/2013/05/testing-on-toilet-dont-overuse-mocks.html).
217
218## Testing using emulators
219
220Some of the client libraries provided in `cloud.google.com/go` support running
221against a service emulator. The concept is similar to that of using fakes,
222mentioned above, but the server is managed for you. You just need to start it up
223and instruct the client library to talk to the emulator by setting a service
224specific emulator environment variable. Current services/environment-variables
225are:
226
227- bigtable: `BIGTABLE_EMULATOR_HOST`
228- datastore: `DATASTORE_EMULATOR_HOST`
229- firestore: `FIRESTORE_EMULATOR_HOST`
230- pubsub: `PUBSUB_EMULATOR_HOST`
231- spanner: `SPANNER_EMULATOR_HOST`
232- storage: `STORAGE_EMULATOR_HOST`
233  - Although the storage client supports an emulator environment variable there is no official emulator provided by gcloud.
234
235For more information on emulators please refer to the
236[gcloud documentation](https://cloud.google.com/sdk/gcloud/reference/beta/emulators).
237