From 38dc0d8deea9d718e601e4092afe53ff5274e267 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Catt=C4=AB=20Cr=C5=ABd=C4=93l=C4=93s?= <17695588+wzy9607@users.noreply.github.com> Date: Tue, 13 Feb 2024 16:13:05 +0800 Subject: [PATCH] fix: add some unittest (#11) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add some unittest * . --------- Signed-off-by: Cattī Crūdēlēs <17695588+wzy9607@users.noreply.github.com> --- .golangci.yml | 1 + .mockery.yaml | 7 + acknowledger.go | 15 ++- acknowledger_test.go | 200 +++++++++++++++++++++++++++++ channel.go | 7 +- codecov.yml | 2 + context_test.go | 47 +++++++ go.mod | 3 + go.sum | 8 ++ mocks/amqp091/mock_Acknowledger.go | 174 +++++++++++++++++++++++++ 10 files changed, 455 insertions(+), 9 deletions(-) create mode 100644 .mockery.yaml create mode 100644 acknowledger_test.go create mode 100644 context_test.go create mode 100644 mocks/amqp091/mock_Acknowledger.go diff --git a/.golangci.yml b/.golangci.yml index d6469e4..46c1acf 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -216,6 +216,7 @@ issues: - funlen - gomnd - nakedret + - spancheck - varnamelen max-issues-per-linter: 0 max-same-issues: 0 diff --git a/.mockery.yaml b/.mockery.yaml new file mode 100644 index 0000000..5499b0a --- /dev/null +++ b/.mockery.yaml @@ -0,0 +1,7 @@ +with-expecter: true +dir: "mocks/{{.PackageName}}" +outpkg: "mock{{.PackageName}}" +packages: + github.com/rabbitmq/amqp091-go: + interfaces: + Acknowledger: diff --git a/acknowledger.go b/acknowledger.go index 90ad2c5..a12efec 100644 --- a/acknowledger.go +++ b/acknowledger.go @@ -5,16 +5,19 @@ import ( "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/trace" + + "github.com/rabbitmq/amqp091-go" ) type acknowledger struct { - ch *Channel - ctx context.Context //nolint:containedctx // consumer needs to retrieve the context via ContextFromDelivery. - span trace.Span + ch *Channel + acker amqp091.Acknowledger // The real acknowledger is amqp091.Channel + ctx context.Context //nolint:containedctx // consumer needs to retrieve the context via ContextFromDelivery. + span trace.Span } func (a *acknowledger) Ack(tag uint64, multiple bool) error { - err := a.ch.Channel.Ack(tag, multiple) + err := a.acker.Ack(tag, multiple) if multiple { a.endMultiple(tag, codes.Ok, "", err) } else { @@ -24,7 +27,7 @@ func (a *acknowledger) Ack(tag uint64, multiple bool) error { } func (a *acknowledger) Nack(tag uint64, multiple, requeue bool) error { - err := a.ch.Channel.Nack(tag, multiple, requeue) + err := a.acker.Nack(tag, multiple, requeue) if multiple { a.endMultiple(tag, codes.Error, "nack", err) } else { @@ -34,7 +37,7 @@ func (a *acknowledger) Nack(tag uint64, multiple, requeue bool) error { } func (a *acknowledger) Reject(tag uint64, requeue bool) error { - err := a.ch.Channel.Reject(tag, requeue) + err := a.acker.Reject(tag, requeue) a.endOne(tag, codes.Error, "reject", err) return err } diff --git a/acknowledger_test.go b/acknowledger_test.go new file mode 100644 index 0000000..ce78700 --- /dev/null +++ b/acknowledger_test.go @@ -0,0 +1,200 @@ +package amqp091otel + +import ( + "context" + "errors" + "slices" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel/codes" + tracesdk "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/sdk/trace/tracetest" + "go.opentelemetry.io/otel/trace" + + mockamqp091 "github.com/wzy9607/amqp091otel/mocks/amqp091" +) + +func initMockTracerProvider() (*tracesdk.TracerProvider, *tracetest.InMemoryExporter) { + exp := tracetest.NewInMemoryExporter() + tp := tracesdk.NewTracerProvider(tracesdk.WithSyncer(exp)) + return tp, exp +} + +func Test_acknowledger(t *testing.T) { + t.Parallel() + type fields struct { + ch *Channel + acker *mockamqp091.MockAcknowledger + ctx context.Context + span trace.Span + } + type args struct { + tag uint64 + multiple bool + requeue bool + } + tests := []struct { + name string + setup func(t *testing.T, fields *fields, args args) (exp *tracetest.InMemoryExporter) + fields fields + args args + wantErr bool + wantNonEndedSpansLen int + wantEndedSpansLen int + wantSpanStatus tracesdk.Status + }{ + { + name: "ack one", + setup: func(t *testing.T, fields *fields, args args) (exp *tracetest.InMemoryExporter) { + t.Helper() + tp, exp := initMockTracerProvider() + _, fields.ch.spanMap[args.tag] = tp.Tracer("test").Start(fields.ctx, "should end 1") + _, fields.ch.spanMap[args.tag+1] = tp.Tracer("test").Start(fields.ctx, "should not end") + fields.span = fields.ch.spanMap[args.tag] + + fields.acker.EXPECT().Ack(args.tag, args.multiple).Return(nil) + return exp + }, + args: args{ + tag: 2, + }, + wantNonEndedSpansLen: 1, + wantEndedSpansLen: 1, + wantSpanStatus: tracesdk.Status{Code: codes.Ok, Description: ""}, + }, { + name: "ack multiple", + setup: func(t *testing.T, fields *fields, args args) (exp *tracetest.InMemoryExporter) { + t.Helper() + tp, exp := initMockTracerProvider() + _, fields.ch.spanMap[args.tag] = tp.Tracer("test").Start(fields.ctx, "should end 1") + _, fields.ch.spanMap[args.tag-1] = tp.Tracer("test").Start(fields.ctx, "should end 2") + _, fields.ch.spanMap[args.tag+1] = tp.Tracer("test").Start(fields.ctx, "should not end") + fields.span = fields.ch.spanMap[args.tag] + + fields.acker.EXPECT().Ack(args.tag, args.multiple).Return(nil) + return exp + }, + args: args{ + tag: 2, + multiple: true, + }, + wantNonEndedSpansLen: 1, + wantEndedSpansLen: 2, + wantSpanStatus: tracesdk.Status{Code: codes.Ok, Description: ""}, + }, { + name: "nack one", + setup: func(t *testing.T, fields *fields, args args) (exp *tracetest.InMemoryExporter) { + t.Helper() + tp, exp := initMockTracerProvider() + _, fields.ch.spanMap[args.tag] = tp.Tracer("test").Start(fields.ctx, "should end 1") + _, fields.ch.spanMap[args.tag+1] = tp.Tracer("test").Start(fields.ctx, "should not end") + fields.span = fields.ch.spanMap[args.tag] + + fields.acker.EXPECT().Nack(args.tag, args.multiple, args.requeue).Return(nil) + return exp + }, + args: args{ + tag: 2, + }, + wantNonEndedSpansLen: 1, + wantEndedSpansLen: 1, + wantSpanStatus: tracesdk.Status{Code: codes.Error, Description: "nack"}, + }, { + name: "nack multiple, got error", + setup: func(t *testing.T, fields *fields, args args) (exp *tracetest.InMemoryExporter) { + t.Helper() + tp, exp := initMockTracerProvider() + _, fields.ch.spanMap[args.tag] = tp.Tracer("test").Start(fields.ctx, "should end 1") + _, fields.ch.spanMap[args.tag-1] = tp.Tracer("test").Start(fields.ctx, "should end 2") + _, fields.ch.spanMap[args.tag+1] = tp.Tracer("test").Start(fields.ctx, "should not end") + fields.span = fields.ch.spanMap[args.tag] + + fields.acker.EXPECT().Nack(args.tag, args.multiple, args.requeue).Return(errors.New("some error")) + return exp + }, + args: args{ + tag: 2, + multiple: true, + }, + wantErr: true, + wantNonEndedSpansLen: 1, + wantEndedSpansLen: 2, + wantSpanStatus: tracesdk.Status{Code: codes.Error, Description: "nack"}, + }, { + name: "reject, got error", + setup: func(t *testing.T, fields *fields, args args) (exp *tracetest.InMemoryExporter) { + t.Helper() + tp, exp := initMockTracerProvider() + _, fields.ch.spanMap[args.tag] = tp.Tracer("test").Start(fields.ctx, "should end 1") + _, fields.ch.spanMap[args.tag+1] = tp.Tracer("test").Start(fields.ctx, "should not end") + fields.span = fields.ch.spanMap[args.tag] + + fields.acker.EXPECT().Reject(args.tag, args.requeue).Return(errors.New("some error")) + return exp + }, + args: args{ + tag: 2, + }, + wantErr: true, + wantNonEndedSpansLen: 1, + wantEndedSpansLen: 1, + wantSpanStatus: tracesdk.Status{Code: codes.Error, Description: "reject"}, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + tt.fields = fields{ + ch: &Channel{spanMap: make(map[uint64]trace.Span)}, + acker: mockamqp091.NewMockAcknowledger(t), + ctx: context.Background(), + } + exp := tt.setup(t, &tt.fields, tt.args) + a := &acknowledger{ + ch: tt.fields.ch, + acker: tt.fields.acker, + ctx: tt.fields.ctx, + span: tt.fields.span, + } + var err error + switch { + case strings.HasPrefix(tt.name, "ack"): + err = a.Ack(tt.args.tag, tt.args.multiple) + case strings.HasPrefix(tt.name, "nack"): + err = a.Nack(tt.args.tag, tt.args.multiple, tt.args.requeue) + case strings.HasPrefix(tt.name, "reject"): + err = a.Reject(tt.args.tag, tt.args.requeue) + default: + t.Fatalf("unknown test case: %s", tt.name) + } + if !tt.wantErr { + require.NoError(t, err) + } else { + require.Error(t, err) + } + + spans := exp.GetSpans() + // only check ended spans + spans = slices.DeleteFunc(spans, func(s tracetest.SpanStub) bool { return s.EndTime.IsZero() }) + assert.Len(t, spans, tt.wantEndedSpansLen) + for _, span := range spans { + assert.Truef(t, strings.HasPrefix(span.Name, "should end"), "span %s is not expected to be ended", span.Name) + assert.Equal(t, tt.wantSpanStatus, span.Status) + if tt.wantErr { + assert.Len(t, span.Events, 1) + } else { + assert.Empty(t, span.Events) + } + } + + assert.Lenf(t, tt.fields.ch.spanMap, tt.wantNonEndedSpansLen, "spanMap should only have non-ended spans") + for tag := range tt.fields.ch.spanMap { + assert.Greater(t, tag, tt.args.tag) + } + }) + } +} diff --git a/channel.go b/channel.go index af9553e..de8910c 100644 --- a/channel.go +++ b/channel.go @@ -106,9 +106,10 @@ func (ch *Channel) startConsumerSpan(msg *amqp091.Delivery, queue string, operat ctx, span := ch.cfg.Tracer.Start(parentCtx, //nolint:spancheck // span ends when msg is ack/nack/rejected ch.nameWhenConsume(queue), opts...) msg.Acknowledger = &acknowledger{ - ch: ch, - ctx: ctx, - span: span, + ch: ch, + acker: ch.Channel, + ctx: ctx, + span: span, } ch.m.Lock() diff --git a/codecov.yml b/codecov.yml index 816d8cc..f8080b9 100644 --- a/codecov.yml +++ b/codecov.yml @@ -9,3 +9,5 @@ coverage: target: 80% threshold: 2% informational: true +ignore: + - "mocks/**" diff --git a/context_test.go b/context_test.go new file mode 100644 index 0000000..526b2da --- /dev/null +++ b/context_test.go @@ -0,0 +1,47 @@ +package amqp091otel + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/rabbitmq/amqp091-go" +) + +func TestContextFromDelivery(t *testing.T) { + t.Parallel() + type key struct{} + ctxInst := context.WithValue(context.Background(), key{}, "value") + type args struct { + msg amqp091.Delivery + } + tests := []struct { + name string + args args + want context.Context + }{ + { + name: "instrumented context", + args: args{ + msg: amqp091.Delivery{ + Acknowledger: &acknowledger{ctx: ctxInst}, + }, + }, + want: ctxInst, + }, { + name: "background context", + args: args{ + msg: amqp091.Delivery{}, + }, + want: context.Background(), + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + assert.Equalf(t, tt.want, ContextFromDelivery(tt.args.msg), "ContextFromDelivery(%v)", tt.args.msg) + }) + } +} diff --git a/go.mod b/go.mod index f02e181..8a6d720 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/rabbitmq/amqp091-go v1.9.0 github.com/stretchr/testify v1.8.4 go.opentelemetry.io/otel v1.23.1 + go.opentelemetry.io/otel/sdk v1.23.1 go.opentelemetry.io/otel/trace v1.23.1 ) @@ -14,6 +15,8 @@ require ( github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/objx v0.5.1 // indirect go.opentelemetry.io/otel/metric v1.23.1 // indirect + golang.org/x/sys v0.17.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index dd22769..4ee0a25 100644 --- a/go.sum +++ b/go.sum @@ -18,18 +18,26 @@ github.com/rabbitmq/amqp091-go v1.9.0 h1:qrQtyzB4H8BQgEuJwhmVQqVHB9O4+MNDJCCAcpc github.com/rabbitmq/amqp091-go v1.9.0/go.mod h1:+jPrT9iY2eLjRaMSRHUhc3z14E/l85kv/f+6luSD3pc= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.1 h1:4VhoImhV/Bm0ToFkXFi8hXNXwpDRZ/ynw3amt82mzq0= +github.com/stretchr/objx v0.5.1/go.mod h1:/iHQpkQwBD6DLUmQ4pE+s1TXdob1mORJ4/UFdrifcy0= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= go.opentelemetry.io/otel v1.23.1 h1:Za4UzOqJYS+MUczKI320AtqZHZb7EqxO00jAHE0jmQY= go.opentelemetry.io/otel v1.23.1/go.mod h1:Td0134eafDLcTS4y+zQ26GE8u3dEuRBiBCTUIRHaikA= go.opentelemetry.io/otel/metric v1.23.1 h1:PQJmqJ9u2QaJLBOELl1cxIdPcpbwzbkjfEyelTl2rlo= go.opentelemetry.io/otel/metric v1.23.1/go.mod h1:mpG2QPlAfnK8yNhNJAxDZruU9Y1/HubbC+KyH8FaCWI= +go.opentelemetry.io/otel/sdk v1.23.1 h1:O7JmZw0h76if63LQdsBMKQDWNb5oEcOThG9IrxscV+E= +go.opentelemetry.io/otel/sdk v1.23.1/go.mod h1:LzdEVR5am1uKOOwfBWFef2DCi1nu3SA8XQxx2IerWFk= go.opentelemetry.io/otel/trace v1.23.1 h1:4LrmmEd8AU2rFvU1zegmvqW7+kWarxtNOPyeL6HmYY8= go.opentelemetry.io/otel/trace v1.23.1/go.mod h1:4IpnpJFwr1mo/6HL8XIPJaE9y0+u1KcVmuW7dwFSVrI= go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/mocks/amqp091/mock_Acknowledger.go b/mocks/amqp091/mock_Acknowledger.go new file mode 100644 index 0000000..096b7a5 --- /dev/null +++ b/mocks/amqp091/mock_Acknowledger.go @@ -0,0 +1,174 @@ +// Code generated by mockery v2.41.0. DO NOT EDIT. + +package mockamqp091 + +import mock "github.com/stretchr/testify/mock" + +// MockAcknowledger is an autogenerated mock type for the Acknowledger type +type MockAcknowledger struct { + mock.Mock +} + +type MockAcknowledger_Expecter struct { + mock *mock.Mock +} + +func (_m *MockAcknowledger) EXPECT() *MockAcknowledger_Expecter { + return &MockAcknowledger_Expecter{mock: &_m.Mock} +} + +// Ack provides a mock function with given fields: tag, multiple +func (_m *MockAcknowledger) Ack(tag uint64, multiple bool) error { + ret := _m.Called(tag, multiple) + + if len(ret) == 0 { + panic("no return value specified for Ack") + } + + var r0 error + if rf, ok := ret.Get(0).(func(uint64, bool) error); ok { + r0 = rf(tag, multiple) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockAcknowledger_Ack_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Ack' +type MockAcknowledger_Ack_Call struct { + *mock.Call +} + +// Ack is a helper method to define mock.On call +// - tag uint64 +// - multiple bool +func (_e *MockAcknowledger_Expecter) Ack(tag interface{}, multiple interface{}) *MockAcknowledger_Ack_Call { + return &MockAcknowledger_Ack_Call{Call: _e.mock.On("Ack", tag, multiple)} +} + +func (_c *MockAcknowledger_Ack_Call) Run(run func(tag uint64, multiple bool)) *MockAcknowledger_Ack_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(uint64), args[1].(bool)) + }) + return _c +} + +func (_c *MockAcknowledger_Ack_Call) Return(_a0 error) *MockAcknowledger_Ack_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockAcknowledger_Ack_Call) RunAndReturn(run func(uint64, bool) error) *MockAcknowledger_Ack_Call { + _c.Call.Return(run) + return _c +} + +// Nack provides a mock function with given fields: tag, multiple, requeue +func (_m *MockAcknowledger) Nack(tag uint64, multiple bool, requeue bool) error { + ret := _m.Called(tag, multiple, requeue) + + if len(ret) == 0 { + panic("no return value specified for Nack") + } + + var r0 error + if rf, ok := ret.Get(0).(func(uint64, bool, bool) error); ok { + r0 = rf(tag, multiple, requeue) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockAcknowledger_Nack_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Nack' +type MockAcknowledger_Nack_Call struct { + *mock.Call +} + +// Nack is a helper method to define mock.On call +// - tag uint64 +// - multiple bool +// - requeue bool +func (_e *MockAcknowledger_Expecter) Nack(tag interface{}, multiple interface{}, requeue interface{}) *MockAcknowledger_Nack_Call { + return &MockAcknowledger_Nack_Call{Call: _e.mock.On("Nack", tag, multiple, requeue)} +} + +func (_c *MockAcknowledger_Nack_Call) Run(run func(tag uint64, multiple bool, requeue bool)) *MockAcknowledger_Nack_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(uint64), args[1].(bool), args[2].(bool)) + }) + return _c +} + +func (_c *MockAcknowledger_Nack_Call) Return(_a0 error) *MockAcknowledger_Nack_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockAcknowledger_Nack_Call) RunAndReturn(run func(uint64, bool, bool) error) *MockAcknowledger_Nack_Call { + _c.Call.Return(run) + return _c +} + +// Reject provides a mock function with given fields: tag, requeue +func (_m *MockAcknowledger) Reject(tag uint64, requeue bool) error { + ret := _m.Called(tag, requeue) + + if len(ret) == 0 { + panic("no return value specified for Reject") + } + + var r0 error + if rf, ok := ret.Get(0).(func(uint64, bool) error); ok { + r0 = rf(tag, requeue) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockAcknowledger_Reject_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Reject' +type MockAcknowledger_Reject_Call struct { + *mock.Call +} + +// Reject is a helper method to define mock.On call +// - tag uint64 +// - requeue bool +func (_e *MockAcknowledger_Expecter) Reject(tag interface{}, requeue interface{}) *MockAcknowledger_Reject_Call { + return &MockAcknowledger_Reject_Call{Call: _e.mock.On("Reject", tag, requeue)} +} + +func (_c *MockAcknowledger_Reject_Call) Run(run func(tag uint64, requeue bool)) *MockAcknowledger_Reject_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(uint64), args[1].(bool)) + }) + return _c +} + +func (_c *MockAcknowledger_Reject_Call) Return(_a0 error) *MockAcknowledger_Reject_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockAcknowledger_Reject_Call) RunAndReturn(run func(uint64, bool) error) *MockAcknowledger_Reject_Call { + _c.Call.Return(run) + return _c +} + +// NewMockAcknowledger creates a new instance of MockAcknowledger. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockAcknowledger(t interface { + mock.TestingT + Cleanup(func()) +}) *MockAcknowledger { + mock := &MockAcknowledger{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +}