go.uber.org/yarpc@v1.72.1/encoding/thrift/thriftrw-plugin-yarpc/gomock_test.go (about) 1 // Copyright (c) 2022 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package main 22 23 import ( 24 "context" 25 "errors" 26 "strings" 27 "testing" 28 29 "github.com/golang/mock/gomock" 30 "github.com/stretchr/testify/assert" 31 "go.uber.org/thriftrw/ptr" 32 "go.uber.org/yarpc" 33 "go.uber.org/yarpc/encoding/thrift/thriftrw-plugin-yarpc/internal/tests/atomic/readonlystoretest" 34 "go.uber.org/yarpc/encoding/thrift/thriftrw-plugin-yarpc/internal/tests/atomic/storetest" 35 "go.uber.org/yarpc/encoding/thrift/thriftrw-plugin-yarpc/internal/tests/common/baseservicetest" 36 "go.uber.org/yarpc/encoding/thrift/thriftrw-plugin-yarpc/internal/tests/common/emptyservicetest" 37 "go.uber.org/yarpc/encoding/thrift/thriftrw-plugin-yarpc/internal/tests/common/extendemptytest" 38 "go.uber.org/yarpc/encoding/thrift/thriftrw-plugin-yarpc/internal/tests/common/extendonlytest" 39 ) 40 41 func TestMockClients(t *testing.T) { 42 ctx := context.Background() 43 44 tests := []struct { 45 desc string 46 withController func(*gomock.Controller) 47 48 wantStatus FakeTestStatus 49 wantPanic interface{} 50 wantErrorsLike []string 51 }{ 52 { 53 desc: "empty", 54 withController: func(ctrl *gomock.Controller) { 55 emptyservicetest.NewMockClient(ctrl).EXPECT() 56 }, 57 }, 58 { 59 desc: "extends empty: unexpected", 60 withController: func(ctrl *gomock.Controller) { 61 c := extendemptytest.NewMockClient(ctrl) 62 c.Hello(ctx) 63 }, 64 wantStatus: Fatal, 65 wantErrorsLike: []string{"Unexpected call"}, 66 }, 67 { 68 desc: "extends empty: expected:", 69 withController: func(ctrl *gomock.Controller) { 70 c := extendemptytest.NewMockClient(ctrl) 71 c.EXPECT().Hello(gomock.Any()).Return(nil) 72 assert.NoError(t, c.Hello(ctx)) 73 }, 74 }, 75 { 76 desc: "extends empty: missing", 77 withController: func(ctrl *gomock.Controller) { 78 c := extendemptytest.NewMockClient(ctrl) 79 c.EXPECT().Hello(gomock.Any()).Return(nil) 80 }, 81 wantStatus: Fatal, 82 wantErrorsLike: []string{ 83 "missing call(s) to [*extendemptytest.MockClient.Hello(is anything)", 84 "aborting test due to missing call(s)", 85 }, 86 }, 87 { 88 desc: "extends only: unexpected", 89 withController: func(ctrl *gomock.Controller) { 90 c := extendonlytest.NewMockClient(ctrl) 91 c.Healthy(ctx) 92 }, 93 wantStatus: Fatal, 94 wantErrorsLike: []string{"Unexpected call"}, 95 }, 96 { 97 desc: "extends only: expected:", 98 withController: func(ctrl *gomock.Controller) { 99 c := extendonlytest.NewMockClient(ctrl) 100 c.EXPECT().Healthy(gomock.Any()).Return(true, nil) 101 healthy, err := c.Healthy(ctx) 102 assert.NoError(t, err) 103 assert.True(t, healthy) 104 }, 105 }, 106 { 107 desc: "extends only: missing", 108 withController: func(ctrl *gomock.Controller) { 109 c := extendonlytest.NewMockClient(ctrl) 110 c.EXPECT().Healthy(gomock.Any()).Return(false, nil) 111 }, 112 wantStatus: Fatal, 113 wantErrorsLike: []string{ 114 "missing call(s) to [*extendonlytest.MockClient.Healthy(is anything)", 115 "aborting test due to missing call(s)", 116 }, 117 }, 118 { 119 desc: "base: expected with options", 120 withController: func(ctrl *gomock.Controller) { 121 c := baseservicetest.NewMockClient(ctrl) 122 c.EXPECT().Healthy(gomock.Any(), gomock.Any()).Return(true, nil) 123 124 // NOTE: Each option has to have a different argument in the 125 // EXPECT call. We should figure out an API to add assertions 126 // on options. See https://github.com/yarpc/yarpc-go/issues/683 127 128 healthy, err := c.Healthy(ctx, yarpc.WithHeader("key", "value")) 129 assert.True(t, healthy) 130 assert.NoError(t, err) 131 }, 132 }, 133 { 134 desc: "store: healthy: unexpected call", 135 withController: func(ctrl *gomock.Controller) { 136 c := storetest.NewMockClient(ctrl) 137 c.Healthy(ctx) 138 }, 139 wantStatus: Fatal, 140 wantErrorsLike: []string{"Unexpected call"}, 141 }, 142 { 143 desc: "store: healthy: expected call", 144 withController: func(ctrl *gomock.Controller) { 145 c := storetest.NewMockClient(ctrl) 146 c.EXPECT().Healthy(gomock.Any()).Return(true, nil) 147 148 result, err := c.Healthy(ctx) 149 assert.NoError(t, err, "mock should return no error") 150 assert.True(t, result, "mock should return true") 151 }, 152 }, 153 { 154 desc: "store: healthy: expected call: throw error", 155 withController: func(ctrl *gomock.Controller) { 156 c := storetest.NewMockClient(ctrl) 157 c.EXPECT().Healthy(gomock.Any()).Return(false, errors.New("great sadness")) 158 159 _, err := c.Healthy(ctx) 160 assert.Equal(t, errors.New("great sadness"), err) 161 }, 162 }, 163 { 164 desc: "readonly store: integer: missing", 165 withController: func(ctrl *gomock.Controller) { 166 c := readonlystoretest.NewMockClient(ctrl) 167 c.EXPECT().Integer(gomock.Any(), ptr.String("foo")).Return(int64(42), nil) 168 }, 169 wantStatus: Fatal, 170 wantErrorsLike: []string{ 171 "missing call(s) to [*readonlystoretest.MockClient.Integer(is anything", 172 "aborting test due to missing call(s)", 173 }, 174 }, 175 { 176 desc: "store: integer: expected", 177 withController: func(ctrl *gomock.Controller) { 178 c := storetest.NewMockClient(ctrl) 179 c.EXPECT().Integer(gomock.Any(), ptr.String("foo")).Return(int64(42), nil) 180 result, err := c.Integer(ctx, ptr.String("foo")) 181 assert.NoError(t, err) 182 assert.Equal(t, int64(42), result) 183 }, 184 }, 185 { 186 desc: "store: forget: unexpected", 187 withController: func(ctrl *gomock.Controller) { 188 c := storetest.NewMockClient(ctrl) 189 c.Forget(ctx, ptr.String("hello")) 190 }, 191 wantStatus: Fatal, 192 wantErrorsLike: []string{"Unexpected call"}, 193 }, 194 { 195 desc: "store: forget: expected", 196 withController: func(ctrl *gomock.Controller) { 197 c := storetest.NewMockClient(ctrl) 198 c.EXPECT().Forget(gomock.Any(), ptr.String("hello")).Return(nil, errors.New("great sadness")) 199 200 _, err := c.Forget(ctx, ptr.String("hello")) 201 assert.Equal(t, errors.New("great sadness"), err) 202 }, 203 }, 204 { 205 desc: "store: forget: missing", 206 withController: func(ctrl *gomock.Controller) { 207 c := storetest.NewMockClient(ctrl) 208 c.EXPECT().Forget(gomock.Any(), ptr.String("hello")).Return(nil, errors.New("great sadness")) 209 }, 210 wantStatus: Fatal, 211 wantErrorsLike: []string{ 212 "missing call(s) to [*storetest.MockClient.Forget(is anything", 213 "aborting test due to missing call(s)", 214 }, 215 }, 216 } 217 218 for _, tt := range tests { 219 t.Run(tt.desc, func(t *testing.T) { 220 result := withFakeTestReporter(func(fakeT gomock.TestReporter) { 221 mockCtrl := gomock.NewController(fakeT) 222 defer mockCtrl.Finish() 223 224 tt.withController(mockCtrl) 225 }) 226 227 if !assert.Equal(t, tt.wantStatus, result.Status, "expected %v, got %v", tt.wantStatus, result.Status) { 228 if result.Status == Panicked { 229 t.Fatalf("panicked: %v\n%v", result.Panic, result.PanicTrace) 230 } 231 } 232 233 assert.Equal(t, tt.wantPanic, result.Panic) 234 assertErrorsMatch(t, tt.wantErrorsLike, result.Errors) 235 }) 236 } 237 } 238 239 func assertErrorsMatch(t *testing.T, wantErrorsLike, errors []string) { 240 var unexpectedErrors []string 241 for _, err := range errors { 242 before := len(wantErrorsLike) 243 wantErrorsLike = removeMatchingError(wantErrorsLike, err) 244 if len(wantErrorsLike) == before { 245 unexpectedErrors = append(unexpectedErrors, err) 246 continue 247 } 248 } 249 250 if len(wantErrorsLike) > 0 { 251 msg := "expected but did not receive errors like:" 252 for _, m := range wantErrorsLike { 253 msg += "\n - " + indentTail(4, m) 254 } 255 t.Errorf(msg) 256 } 257 258 if len(unexpectedErrors) > 0 { 259 msg := "received unexpected errors:" 260 for _, err := range unexpectedErrors { 261 msg += "\n - " + indentTail(4, err) 262 } 263 t.Error(msg) 264 } 265 } 266 267 func removeMatchingError(matchers []string, err string) []string { 268 match := -1 269 for i, m := range matchers { 270 if strings.Contains(err, m) { 271 match = i 272 break 273 } 274 } 275 276 if match < 0 { 277 return matchers 278 } 279 280 matchers = append(matchers[:match], matchers[match+1:]...) 281 return matchers 282 } 283 284 // indentTail prepends the given number of spaces to all lines following the 285 // first line of the given string. 286 func indentTail(spaces int, s string) string { 287 prefix := strings.Repeat(" ", spaces) 288 lines := strings.Split(s, "\n") 289 for i, line := range lines[1:] { 290 lines[i+1] = prefix + line 291 } 292 return strings.Join(lines, "\n") 293 }