1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
|
# Testing Code that depends on google.golang.org/api
The client libraries generated as a part of `google.golang.org/api` all take
the approach of returning concrete types instead of interfaces. That way, new
fields and methods can be added to the libraries without breaking users. This
document will go over some patterns that can be used to test code that depends
on the these libraries.
## Testing HTTP services using fakes
*Note*: You can see the full
[example code using a fake here](https://github.com/googleapis/google-api-go-client/tree/main/internal/examples/fake).
The services found in `google.golang.org/api` are all HTTP based.
Interactions with HTTP services can be faked by serving up your own in-memory
server within your test. One benefit of using this approach is that you don’t
need to define an interface in your runtime code; you can keep using the
concrete struct types returned by the client library. For example, take a look
at the following function:
```go
import (
"fmt"
"os"
"google.golang.org/api/translate/v3"
)
// TranslateText translates text to the given language using the provided
// service.
func TranslateText(service *translate.Service, text, language string) (string, error) {
parent := fmt.Sprintf("projects/%s/locations/global", os.Getenv("GOOGLE_CLOUD_PROJECT"))
req := &translate.TranslateTextRequest{
TargetLanguageCode: language,
Contents: []string{text},
}
resp, err := service.Projects.Locations.TranslateText(parent, req).Do()
if err != nil {
return "", fmt.Errorf("unable to translate text: %v", err)
}
return resp.Translations[0].TranslatedText, nil
}
```
To fake HTTP interactions we can make use of the `httptest` package found in the
standard library. The server URL obtained from creating the test server can be
passed to the service constructor with the use of `option.WithEndpoint`. This
instructs the service to route all traffic to your test sever rather than the
live service. Here is an example of what doing this looks like when calling the
above function:
```go
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"google.golang.org/api/option"
"google.golang.org/api/translate/v3"
)
func TestTranslateText(t *testing.T) {
ctx := context.Background()
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
resp := &translate.TranslateTextResponse{
Translations: []*translate.Translation{
{TranslatedText: "Hello World"},
},
}
b, err := json.Marshal(resp)
if err != nil {
http.Error(w, "unable to marshal request: "+err.Error(), http.StatusBadRequest)
return
}
w.Write(b)
}))
defer ts.Close()
svc, err := translate.NewService(ctx, option.WithoutAuthentication(), option.WithEndpoint(ts.URL))
if err != nil {
t.Fatalf("unable to create client: %v", err)
}
text, err := TranslateText(svc, "Hola Mundo", "en-US")
if err != nil {
t.Fatal(err)
}
if text != "Hello World" {
t.Fatalf("got %q, want Hello World", text)
}
}
```
## Testing using mocks
*Note*: You can see the full
[example code using a mocks here](https://github.com/googleapis/google-api-go-client/tree/main/internal/examples/mock).
When mocking code you need to work with interfaces. Because the services in
`google.golang.org/api` use the builder pattern to construct and execute
requests it can be tedious to create low-level interfaces that match methods
found on the services directly. Although this can be done and you can find
examples of this in full code linked above. Another approach that will keep your
interfaces cleaner is to create a high-level interface that accepts all of the
required input and returns the desired outputs. This hides the complexity of the
services builder pattern. This concept is sometimes referred to as the facade
pattern. Here is an example of a high level interface for the `TranslateText`
method:
```go
// TranslateService is a facade of a `translate.Service`, specifically used to
// for translating text.
type TranslateService interface {
TranslateText(text, language string) (string, error)
}
// TranslateTextHighLevel translates text to the given language using the
// provided service.
func TranslateTextHighLevel(service TranslateService, text, language string) (string, error) {
return service.TranslateText(text, language)
}
```
This interface allows a concrete `translate.Service` to be wrapped and passed to
the function in production and for a mock implementation to be passed in during
testing. Here is what it would look like to create a wrapper around a `translate.Service`
to fullfil the `TranslateService` interface:
```go
import (
"context"
"fmt"
"log"
"os"
"google.golang.org/api/option"
"google.golang.org/api/translate/v3"
)
type translateService struct {
svc *translate.Service
}
// NewTranslateService creates a TranslateService.
func NewTranslateService(ctx context.Context, opts ...option.ClientOption) TranslateService {
svc, err := translate.NewService(ctx, opts...)
if err != nil {
log.Fatalf("unable to create translate service, shutting down: %v", err)
}
return &translateService{svc}
}
func (t *translateService) TranslateText(text, language string) (string, error) {
parent := fmt.Sprintf("projects/%s/locations/global", os.Getenv("GOOGLE_CLOUD_PROJECT"))
resp, err := t.svc.Projects.Locations.TranslateText(parent, &translate.TranslateTextRequest{
TargetLanguageCode: language,
Contents: []string{text},
}).Do()
if err != nil {
return "", fmt.Errorf("unable to translate text: %v", err)
}
return resp.Translations[0].TranslatedText, nil
}
```
Let’s take a look at what it might look like to define a lightweight mock for
the `TranslateService` interface.
```go
import "testing"
// mockService fulfills the TranslateService interface.
type mockService struct{}
func (*mockService) TranslateText(text, language string) (string, error) {
return "Hello World", nil
}
func TestTranslateTextHighLevel(t *testing.T) {
svc := &mockService{}
text, err := TranslateTextHighLevel(svc, "Hola Mundo", "en-US")
if err != nil {
t.Fatal(err)
}
if text != "Hello World" {
t.Fatalf("got %q, want Hello World", text)
}
}
```
If you prefer to not write your own mocks there are mocking frameworks such as
[golang/mock](https://github.com/golang/mock) which can generate mocks for you
from an interface. As a word of caution though, try to not
[overuse mocks](https://testing.googleblog.com/2013/05/testing-on-toilet-dont-overuse-mocks.html).
|