Skip to content

Commit 53131eb

Browse files
ermyaroleg-jukovec
authored andcommitted
tests: MockDoer refactoring
MockDoer become interface that allows controlling and testing response and request flows. Closes #487
1 parent 76e8295 commit 53131eb

9 files changed

Lines changed: 210 additions & 77 deletions

File tree

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,19 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release.
3131
* Method String() for type decimal.Decimal (#322).
3232
* New `T` interface compatible with testing.T methods
3333
to make testing easier, `test_helpers` updated with it (#474).
34+
* New `MockDoer` interface for custom `Doer` testing with builder pattern
35+
methods: `AddResponse`, `AddResponseRaw`, `AddResponseError`, `Requests`.
36+
* New `MockRequestNamed` type for verifying specific requests in tests.
3437

3538
### Changed
3639

3740
* Required Go version is `1.24` now (#456).
41+
* `test_helpers.MockDoer` is now an interface instead of a struct. The
42+
`Requests` field became a method `Requests()`. The `NewMockDoer()`
43+
constructor now returns the interface and uses a builder pattern.
44+
Old `NewMockDoer(t, ...interface{})` is removed. Use `NewMockDoer(t)`,
45+
then chain `AddResponseRaw()`, `AddResponseError()`, `AddResponse()`
46+
to configure responses.
3847
* `box.New` returns an error instead of panic (#448).
3948
* Now cases of `<-ctx.Done()` returns wrapped error provided by `ctx.Cause()`.
4049
Allows you compare it using `errors.Is/As` (#457).

MIGRATION.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,3 +363,24 @@ The subpackage has been deleted. You could use `pool` instead.
363363
### test_helpers package
364364

365365
* Renamed `StrangerResponse` to `MockResponse`.
366+
* `MockDoer` is now an interface instead of a struct. Use `NewMockDoer(t)` to
367+
create an instance, then chain `AddResponseRaw()`, `AddResponseError()`,
368+
`AddResponse()` to configure responses. The `Requests` field is now a
369+
method `Requests()` that returns recorded requests.
370+
371+
Before:
372+
```Go
373+
mock := test_helpers.NewMockDoer(t,
374+
test_helpers.NewMockResponse(t, []interface{}{"data"}),
375+
errors.New("some error"),
376+
)
377+
requests := mock.Requests
378+
```
379+
380+
After:
381+
```Go
382+
mock := test_helpers.NewMockDoer(t).
383+
AddResponseRaw([]interface{}{"data"}).
384+
AddResponseError(errors.New("some error"))
385+
requests := mock.Requests()
386+
```

box/box_test.go

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,16 @@ func TestMustNew(t *testing.T) {
2929
func TestMocked_BoxNew(t *testing.T) {
3030
t.Parallel()
3131

32-
mock := test_helpers.NewMockDoer(t,
33-
test_helpers.NewMockResponse(t, "valid"),
34-
)
32+
mock := test_helpers.NewMockDoer(t).
33+
AddResponseRaw("valid")
3534

36-
b, err := box.New(&mock)
35+
b, err := box.New(mock)
3736
require.NoError(t, err)
3837
require.NotNil(t, b)
3938

40-
assert.Empty(t, mock.Requests)
39+
assert.Empty(t, mock.Requests())
4140
_, _ = b.Schema().User().Exists(box.NewInfoRequest().Ctx(), "")
42-
require.Len(t, mock.Requests, 1)
41+
require.Len(t, mock.Requests(), 1)
4342
}
4443

4544
func TestMocked_BoxInfo(t *testing.T) {
@@ -57,10 +56,9 @@ func TestMocked_BoxInfo(t *testing.T) {
5756
"replication": nil,
5857
},
5958
}
60-
mock := test_helpers.NewMockDoer(t,
61-
test_helpers.NewMockResponse(t, data),
62-
)
63-
b := box.MustNew(&mock)
59+
mock := test_helpers.NewMockDoer(t).
60+
AddResponseRaw(data)
61+
b := box.MustNew(mock)
6462

6563
info, err := b.Info()
6664
require.NoError(t, err)
@@ -77,10 +75,9 @@ func TestMocked_BoxSchemaUserInfo(t *testing.T) {
7775
[]interface{}{"read,write,execute", "universe", ""},
7876
},
7977
}
80-
mock := test_helpers.NewMockDoer(t,
81-
test_helpers.NewMockResponse(t, data),
82-
)
83-
b := box.MustNew(&mock)
78+
mock := test_helpers.NewMockDoer(t).
79+
AddResponseRaw(data)
80+
b := box.MustNew(mock)
8481

8582
privs, err := b.Schema().User().Info(context.Background(), "username")
8683
require.NoError(t, err)
@@ -101,11 +98,10 @@ func TestMocked_BoxSchemaUserInfo(t *testing.T) {
10198
func TestMocked_BoxSessionSu(t *testing.T) {
10299
t.Parallel()
103100

104-
mock := test_helpers.NewMockDoer(t,
105-
test_helpers.NewMockResponse(t, []interface{}{}),
106-
errors.New("user not found or supplied credentials are invalid"),
107-
)
108-
b := box.MustNew(&mock)
101+
mock := test_helpers.NewMockDoer(t).
102+
AddResponseRaw([]interface{}{}).
103+
AddResponseError(errors.New("user not found or supplied credentials are invalid"))
104+
b := box.MustNew(mock)
109105

110106
err := b.Session().Su(context.Background(), "admin")
111107
require.NoError(t, err)

box/session_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010
)
1111

1212
func TestBox_Session(t *testing.T) {
13-
b := box.MustNew(th.Ptr(th.NewMockDoer(t)))
13+
b := box.MustNew(th.NewMockDoer(t))
1414
require.NotNil(t, b.Session())
1515
}
1616

go.sum

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
1919
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
2020
github.com/tarantool/go-iproto v1.1.0 h1:HULVOIHsiehI+FnHfM7wMDntuzUddO09DKqu2WnFQ5A=
2121
github.com/tarantool/go-iproto v1.1.0/go.mod h1:LNCtdyZxojUed8SbOiYHoc3v9NvaZTB7p96hUySMlIo=
22-
github.com/tarantool/go-option v1.0.0 h1:+Etw0i3TjsXvADTo5rfZNCfsXe3BfHOs+iVfIrl0Nlo=
23-
github.com/tarantool/go-option v1.0.0/go.mod h1:lXzzeZtL+rPUtLOCDP6ny3FemFBjruG9aHKzNN2bS08=
2422
github.com/tarantool/go-option v1.1.0 h1:ShoOhNsdL41sRpm4hXCRDjV8H0WzPkd4UnKhLKbW//w=
2523
github.com/tarantool/go-option v1.1.0/go.mod h1:hMr9z2JXOWlgdCBpCPSL2nwp8718GKYvNBJ+ZuzJbCo=
2624
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=

schema_test.go

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ func TestGetSchema_ok(t *testing.T) {
4343
FieldsById: make(map[uint32]tarantool.Field),
4444
}
4545

46-
mockDoer := test_helpers.NewMockDoer(t,
47-
test_helpers.NewMockResponse(t, [][]interface{}{
46+
mockDoer := test_helpers.NewMockDoer(t).
47+
AddResponseRaw([][]interface{}{
4848
{
4949
uint32(1),
5050
"skip",
@@ -59,8 +59,8 @@ func TestGetSchema_ok(t *testing.T) {
5959
"",
6060
0,
6161
},
62-
}),
63-
test_helpers.NewMockResponse(t, [][]interface{}{
62+
}).
63+
AddResponseRaw([][]interface{}{
6464
{
6565
uint32(2),
6666
uint32(1),
@@ -69,8 +69,7 @@ func TestGetSchema_ok(t *testing.T) {
6969
uint8(1),
7070
uint8(0),
7171
},
72-
}),
73-
)
72+
})
7473

7574
expectedSchema := tarantool.Schema{
7675
SpacesById: map[uint32]tarantool.Space{
@@ -83,33 +82,35 @@ func TestGetSchema_ok(t *testing.T) {
8382
},
8483
}
8584

86-
schema, err := tarantool.GetSchema(&mockDoer)
85+
schema, err := tarantool.GetSchema(mockDoer)
8786
require.NoError(t, err)
8887
require.Equal(t, expectedSchema, schema)
8988
}
9089

9190
func TestGetSchema_spaces_select_error(t *testing.T) {
92-
mockDoer := test_helpers.NewMockDoer(t, fmt.Errorf("some error"))
91+
mockDoer := test_helpers.NewMockDoer(t).
92+
AddResponseError(fmt.Errorf("some error"))
9393

94-
schema, err := tarantool.GetSchema(&mockDoer)
94+
schema, err := tarantool.GetSchema(mockDoer)
9595
require.EqualError(t, err, "some error")
9696
require.Equal(t, tarantool.Schema{}, schema)
9797
}
9898

9999
func TestGetSchema_index_select_error(t *testing.T) {
100-
mockDoer := test_helpers.NewMockDoer(t,
101-
test_helpers.NewMockResponse(t, [][]interface{}{
102-
{
103-
uint32(1),
104-
"skip",
105-
"name1",
106-
"",
107-
0,
108-
},
109-
}),
110-
fmt.Errorf("some error"))
111-
112-
schema, err := tarantool.GetSchema(&mockDoer)
100+
mockDoer := test_helpers.NewMockDoer(t).
101+
AddResponseRaw(
102+
[][]interface{}{
103+
{
104+
uint32(1),
105+
"skip",
106+
"name1",
107+
"",
108+
0,
109+
},
110+
}).
111+
AddResponseError(fmt.Errorf("some error"))
112+
113+
schema, err := tarantool.GetSchema(mockDoer)
113114
require.EqualError(t, err, "some error")
114115
require.Equal(t, tarantool.Schema{}, schema)
115116
}

test_helpers/doer.go

Lines changed: 63 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,59 +2,96 @@ package test_helpers
22

33
import (
44
"bytes"
5+
"io"
56

67
"github.com/tarantool/go-tarantool/v3"
78
)
89

10+
// MockDoer is an interface for a mock doer used for custom testing.
11+
// It allows building a sequence of responses and inspecting requests
12+
// that were sent via Do calls.
13+
type MockDoer interface {
14+
// AddResponse adds a response with a custom header and a body
15+
// packed inside an io.Reader and returns the MockDoer for chaining.
16+
AddResponse(header tarantool.Header, body io.Reader) MockDoer
17+
// AddResponseRaw adds a response with an empty header and the given
18+
// data, which is encoded with msgpack, and returns the MockDoer for
19+
// chaining.
20+
AddResponseRaw(data interface{}) MockDoer
21+
// AddResponseError adds an error response and returns the MockDoer for
22+
// chaining.
23+
AddResponseError(err error) MockDoer
24+
// Requests returns a slice of requests received via Do calls.
25+
Requests() []tarantool.Request
26+
// Do processes the request and returns a Future with the next
27+
// configured response or error. It calls Fatalf on the test if
28+
// no responses are configured.
29+
Do(req tarantool.Request) tarantool.Future
30+
}
31+
932
type doerResponse struct {
1033
resp *MockResponse
1134
err error
1235
}
1336

14-
// MockDoer is an implementation of the Doer interface
37+
// mockDoer is an implementation of the MockDoer interface
1538
// used for testing purposes.
16-
type MockDoer struct {
17-
// Requests is a slice of received requests.
39+
type mockDoer struct {
40+
// requests is a slice of received requests.
1841
// It could be used to compare incoming requests with expected.
19-
Requests []tarantool.Request
42+
requests []tarantool.Request
2043
responses []doerResponse
2144
t T
2245
}
2346

24-
// NewMockDoer creates a MockDoer by given responses.
25-
// Each response could be one of two types: MockResponse or error.
26-
func NewMockDoer(t T, responses ...interface{}) MockDoer {
27-
t.Helper()
47+
var _ MockDoer = (*mockDoer)(nil)
2848

29-
mockDoer := MockDoer{t: t}
30-
for _, response := range responses {
31-
doerResp := doerResponse{}
32-
33-
switch resp := response.(type) {
34-
case *MockResponse:
35-
doerResp.resp = resp
36-
case error:
37-
doerResp.err = resp
38-
default:
39-
t.Fatalf("unsupported type: %T", response)
40-
}
49+
func (m *mockDoer) Requests() []tarantool.Request {
50+
return m.requests
51+
}
4152

42-
mockDoer.responses = append(mockDoer.responses, doerResp)
53+
func (m *mockDoer) AddResponse(header tarantool.Header, data io.Reader) MockDoer {
54+
resp, err := CreateMockResponse(header, data)
55+
if err != nil {
56+
m.t.Fatalf("failed to create mock response: %v", err)
4357
}
44-
return mockDoer
58+
59+
m.responses = append(m.responses, doerResponse{resp: resp})
60+
return m
61+
}
62+
63+
func (m *mockDoer) AddResponseRaw(data interface{}) MockDoer {
64+
m.responses = append(m.responses, doerResponse{resp: NewMockResponse(m.t, data)})
65+
return m
66+
}
67+
68+
func (m *mockDoer) AddResponseError(err error) MockDoer {
69+
m.responses = append(m.responses, doerResponse{err: err})
70+
return m
71+
}
72+
73+
// NewMockDoer creates a MockDoer for testing. Use AddResponse, AddResponseRaw,
74+
// and AddResponseError methods to configure the sequence of responses.
75+
func NewMockDoer(t T) MockDoer {
76+
t.Helper()
77+
78+
return &mockDoer{t: t}
4579
}
4680

4781
// Do returns a future with the current response or an error.
4882
// It saves the current request into MockDoer.Requests.
49-
func (doer *MockDoer) Do(req tarantool.Request) tarantool.Future {
50-
doer.Requests = append(doer.Requests, req)
51-
83+
func (doer *mockDoer) Do(req tarantool.Request) tarantool.Future {
5284
mockReq := NewMockRequest()
5385
var fut tarantool.Future
5486

5587
if len(doer.responses) == 0 {
56-
doer.t.Fatalf("list of responses is empty")
88+
doer.t.Fatalf("MockDoer.Do: no responses configured, %d Do() calls were made",
89+
len(doer.requests))
90+
91+
return fut
5792
}
93+
94+
doer.requests = append(doer.requests, req)
5895
response := doer.responses[0]
5996

6097
if response.err != nil {

0 commit comments

Comments
 (0)