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