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