You can specify the right order of the function calls especially if the order matters. For example, ProcessVideo requires the right steps to process a video. The expected order is enforced by mock.InOrder.
main.go
//go:generate mockery --name=Processor --dir=. --output=./mocks --outpkg=servicemock --case=snake
// ...
type Processor interface {
Download(ctx context.Context, videoID string) ([]byte, error)
Compress(ctx context.Context, raw []byte) ([]byte, error)
Upload(ctx context.Context, videoID string, compressed []byte) error
}
func ProcessVideo(ctx context.Context, p Processor, videoID string) error {
if videoID == "" {
return fmt.Errorf("videoID must be non-empty")
}
raw, err := p.Download(ctx, videoID)
if err != nil {
return err
}
compressed, err := p.Compress(ctx, raw)
if err != nil {
return err
}
return p.Upload(ctx, videoID, compressed)
}
main_test.go
func TestProcessVideo(t *testing.T) {
ctx := context.Background()
tests := []struct {
name string
videoID string
raw []byte
compressed []byte
}{
{name: "happy_path", videoID: "vid-123", raw: []byte("raw"), compressed: []byte("c")},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mockProc := servicemock.NewProcessor(t)
mock.InOrder(
mockProc.On("Download", mock.Anything, tt.videoID).Return(tt.raw, nil),
mockProc.On("Compress", mock.Anything, tt.raw).Return(tt.compressed, nil),
mockProc.On("Upload", mock.Anything, tt.videoID, tt.compressed).Return(nil),
)
err := ProcessVideo(ctx, mockProc, tt.videoID)
require.NoError(t, err)
})
}
}
Mocked service below:
// Code generated by mockery v2.53.5. DO NOT EDIT.
package servicemock
import (
context "context"
mock "github.com/stretchr/testify/mock"
)
// Processor is an autogenerated mock type for the Processor type
type Processor struct {
mock.Mock
}
// Compress provides a mock function with given fields: ctx, raw
func (_m *Processor) Compress(ctx context.Context, raw []byte) ([]byte, error) {
ret := _m.Called(ctx, raw)
if len(ret) == 0 {
panic("no return value specified for Compress")
}
var r0 []byte
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, []byte) ([]byte, error)); ok {
return rf(ctx, raw)
}
if rf, ok := ret.Get(0).(func(context.Context, []byte) []byte); ok {
r0 = rf(ctx, raw)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]byte)
}
}
if rf, ok := ret.Get(1).(func(context.Context, []byte) error); ok {
r1 = rf(ctx, raw)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Download provides a mock function with given fields: ctx, videoID
func (_m *Processor) Download(ctx context.Context, videoID string) ([]byte, error) {
ret := _m.Called(ctx, videoID)
if len(ret) == 0 {
panic("no return value specified for Download")
}
var r0 []byte
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, string) ([]byte, error)); ok {
return rf(ctx, videoID)
}
if rf, ok := ret.Get(0).(func(context.Context, string) []byte); ok {
r0 = rf(ctx, videoID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]byte)
}
}
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
r1 = rf(ctx, videoID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Upload provides a mock function with given fields: ctx, videoID, compressed
func (_m *Processor) Upload(ctx context.Context, videoID string, compressed []byte) error {
ret := _m.Called(ctx, videoID, compressed)
if len(ret) == 0 {
panic("no return value specified for Upload")
}
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, string, []byte) error); ok {
r0 = rf(ctx, videoID, compressed)
} else {
r0 = ret.Error(0)
}
return r0
}
// NewProcessor creates a new instance of Processor. 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 NewProcessor(t interface {
mock.TestingT
Cleanup(func())
}) *Processor {
mock := &Processor{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}