How to test pubsub message acknowledgement in go? - unit-testing

How to write a unit test which would verify that the message is actually being acked ?
I would like to mock the pubsub message and verify that Ack is called but cant't realize how.
func processMessage(ctx context.Context, msg *pubsub.Message) {
log.Printf("Processing message, data: %s", msg.Data)
msg.Ack()
}

You can do that by using interface and mocking as follows.
// Create an interface that'll be implemented by *pubsub.Message
type message interface{
Ack()
}
// Accept the interface instead of the concrete struct
func processMessage(ctx context.Context, msg message) {
log.Printf("Processing message, data: %s", msg.Data)
msg.Ack()
}
Now, in the test files, create a mocked message and confirm whether Ack is being called. You can use testify/mock for that.

You can create an interface that wraps the message and a function that wraps the receive function type.
// message interface to pass to our message handler
type Message interface {
GetData() []byte
Ack()
}
// real message for pubsub messages
type msg struct {
m *pubsub.Message
}
// returns real message data
func (i *msg) GetData() {
return i.m.Data
}
// acks the real pubsub message
func (i *msg) Ack() {
i.m.Ack()
}
// test message can be passed to the handleMessage function
type testMsg struct {
d []byte
}
// returns the test data
func (t *testMsg) GetData() []byte {
return d
}
// does nothing so we can test our handleMessage function
func (t *testMsg) Ack() {}
func main() {
client, err := pubsub.NewClient(context.Background(), "projectID")
if err != nil {
log.Fatal(err)
}
sub := client.Subscription("pubsub-sub")
sub.Receive(ctx, createHandler())
}
// creates the handler, allows us to pass our interface to the message handler
func createHandler() func(ctx context.Context, m *pubsub.Message) {
// returns the correct function type but uses our message interface
return func(ctx context.Context, m *pubsub.Message) {
handleMessage(ctx, &msg{m})
}
}
// could pass msg or testMsg here because both meet the interface Message
func handleMessage(ctx context.Context, m Message) {
log.Println(m.GetData())
m.Ack()
}

Related

Passing mock as argument in Golang test

This is the code I am testing
func listObjects(cli *client.Client, options clientOptions) ([]BlobObjects, error) {
objects, err := cli.ListBlobObjects(...)
}
In my test setup I do this
type MockClient struct {
MockListBlobObjects func() ([]BlobObjects, error)
}
func (m *MockClient) ListBlobObjects(....) ([]BlobObjects, error) {
// return some mock response
}
And this is my test case
func TestBlobObjects(t *testing.T) {
tests := map[string]struct {
client *MockClient
...
}{
"Test case 1": {
client: &MockClient{
MockListBlobObjects: ....,
},
....
},
....
for testName, test := range tests {
blobs, err := (test.client, clientOptions{})
// make assertions here
}
The problem is test.client. Compiler is telling me
cannot use test.client (variable of type *MockClient) as *client.Client value in argument to listObjects
My hope was I have a mock client and if I call the function under test, then the mock client passed will call the mocked listObjects. This is how I would do in Python.
What should I do in Golang?
You need to create a common interface, which will be implemented by both types
type Client interface {
ListBlobObjects func() ([]BlobObjects, error)
}
type RealClient struct {
}
func (c *RealClient) ListBlobObjects(....) ([]BlobObjects, error) {
// return some response
}
type MockClient struct {
// note: don't put method signature in function body
}
func (m *MockClient) ListBlobObjects(....) ([]BlobObjects, error) {
// return some mock response
}
and then
var client client.Client
client = &RealClient{}
// or
client = &MockClient{}

Golang mockgen "does not implement" interface issue

I have a interface, and used mockgen to create mocks:
type Client {
getBytes(ctx context.Context, token, url string) ([]byte, error)
SendRequest(ctx context.Context, token, id string) (Response, error)
}
type Response {
...
}
I used mockgen to create the mock file, which looks good. The generated mock file is as this:
// Code generated by MockGen. DO NOT EDIT.
// Source: utils/myservice/client.go
// Package mock_myservice is a generated GoMock package.
package mock_myservice
import (
context "context"
reflect "reflect"
gomock "github.com/golang/mock/gomock"
myservice "github.com/robinhoodmarkets/rh/inbox/utils/myservice"
)
// MockClient is a mock of Client interface.
type MockClient struct {
ctrl *gomock.Controller
recorder *MockClientMockRecorder
}
// MockClientMockRecorder is the mock recorder for MockClient.
type MockClientMockRecorder struct {
mock *MockClient
}
// NewMockClient creates a new mock instance.
func NewMockClient(ctrl *gomock.Controller) *MockClient {
mock := &MockClient{ctrl: ctrl}
mock.recorder = &MockClientMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockClient) EXPECT() *MockClientMockRecorder {
return m.recorder
}
// SendRequest mocks base method.
func (m *MockClient) SendRequest(ctx context.Context, token, id string) (myservice.ToggleSettingsItemResponse, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SendRequest", ctx, token, id)
ret0, _ := ret[0].(myservice.ToggleSettingsItemResponse)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// SendRequest indicates an expected call of SendRequest.
func (mr *MockClientMockRecorder) SendRequest(ctx, token, id interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendRequest", reflect.TypeOf((*MockClient)(nil).SendRequest), ctx, token, id)
}
// getBytes mocks base method.
func (m *MockClient) **getBytes**(ctx context.Context, token, url string) ([]byte, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "getBytes", ctx, token, url)
ret0, _ := ret[0].([]byte)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// getBytes indicates an expected call of getBytes.
func (mr *MockClientMockRecorder) getBytes(ctx, token, url interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "getBytes", reflect.TypeOf((*MockClient)(nil).getBytes), ctx, token, url)
}
However, when I tried to used in unittests, i goet this error:
*MockClient does not implement Client (missing getBytes method):
have: mock_myservice.getBytes(context.Context, string, string) ([]byte, error)
want: myservice.getBytes(context.Context, string, string) ([]byte, error)
This is wired since the function is indeed in the mocked file.
Actually I figured out myself:
getBytes is not a public function, as the function names doesn't start with capitalized letter, and hence the implementation is unable to be matched.
This is likely a mockgen issue, as it should give some warning for such usage.

Mock asserting into callbacks

I'm having the following problem when unit testing (working code here]
Assuming we have this:
var (
userDomain UserInterface
Tx TxInterface
)
type UserInterface interface {
Get() (*User, error)
}
type TxInterface interface {
Exec(fn func() error) error
}
type User struct {
ID int64
}
func (u *User) Get() (*User, error) {
return &User{
ID: 1,
}, nil
}
type WithTx struct{}
func (t *WithTx) Exec(fn func() error) error {
/* more logic here */
return fn()
}
Assuming we have the following 2 services:
func GetByID() (*User, error) {
user, err := userDomain.Get()
return user, err
}
func GetByIDWithTx() (*User, error) {
u := &User{}
/** Notice this implementation **/
if err := Tx.Exec(func() error {
user, _ := userDomain.Get()
u = user
return nil
}); err != nil {
log.Print("Tx err", err)
}
return u, nil
}
They are the same but once goes directly to the Get method, the other one runs into a callback fn. So far so go, both works like a charm. Again code is here
I'm trying to mock the userDomain and Tx for unit tests, so my mocks look like this:
var (
getUserFromMock func() (*User, error)
getFromMock func() error
)
type userMock struct{}
func (u *userMock) Get() (*User, error) {
return getUserFromMock()
}
type txMock struct{}
func (t *txMock) Exec(fn func() error) error {
return getFromMock()
}
When testing GetByID I have no problem whatsoever.
func TestGetByID(t *testing.T) {
userDomain = &userMock{}
Tx = &txMock{}
getUserFromMock = func() (*User, error) {
return &User{ID: 3}, nil
}
user, err := GetByID()
if user.ID != 3 {
t.Fatalf("error")
}
if err != nil {
t.Fatalf("error")
}
}
However, When I try to testing GetByIDWithTx, I cannot assert the mock values:
func TestGetByIDWithTx(t *testing.T) {
userDomain = &userMock{}
Tx = &txMock{}
getUserFromMock = func() (*User, error) {
return &User{ID: 4}, nil
}
/**** Mocking callback response ****/
getFromMock = func() error {
return nil
}
user, err := GetByIDWithTx()
if user.ID != 4 {
t.Fatalf("error") /** It fails here **/
}
if err != nil {
t.Fatalf("error")
}
}
I assume, at the moment I'm trying to assert the callback is still running in another routine but I'm just speculating, I don't have any idea what is going on here. I rather not use any library for a spy, I just want to understand how to solve this. Thoughts?
Go PlayGround HERE
You are never calling the fn given to the txmock. Change txMock.Exec to:
type txMock struct{}
func (t *txMock) Exec(fn func() error) error {
return fn()
// return getFromMock()
}
and remove getFromMock() function completely. It will then call getUserFromMock function to get the mocked user.

How to unit test pubsub Receive callback

I have the following production code:
func pullMessages(ctx context.Context, sub *pubsub.Subscription) {
err := sub.Receive(ctx, func(ctx context.Context, msg *pubsub.Message) {
log.Printf("Got message: %q\n", string(msg.Data))
processMessage(msg)
msg.Ack()
})
if err != nil {
log.Printf("Receive: %v", err)
}
}
How can I verify processMessage is actually being called in a unit test ?
Quite simple: make your callback a named function instead of an anonymous one:
func pullMessages(ctx context.Context, sub *pubsub.Subscription) {
if err := sub.Receive(ctx, rcvCallback); err != nil {
log.Printf("Receiving message: %s",err)
}
}
func rcvCallback (ctx context.Context, msg *pubsub.Message) {
log.Printf("Got message: %q\n", string(msg.Data))
processMessage(msg)
msg.Ack()
}
Now, in your unit tests, you can create an instance of a Context and a Message and pass it to your function. However, in this case, that barely makes sense. You do little more than logging and acknowledging the message, functions which should be unit tested by the upstream project. Hence it would make more sense to construct an instance of message and unit test processMessage.

Mock embedded struct in go test

Given:
// FileReader interface for reading from a file
type FileReader interface {
ReadFile(filename string) ([]byte, error)
}
type FileRead struct {}
// ReadFile reads from filename fn using ioutilReadFile
func (fr FileRead) ReadFile(fn string) ([]byte, error) {
return ioutil.ReadFile(fn)
}
type Dev struct {
*FileRead
}
func NewDev() *Dev {
frd := FileRead{}
return &Dev{frd}
}
// Function that does some job
func (dev Dev) DoSomeStuff() {
//...
dev.ReadFile("file")
//...
}
func main () {
doer := NewDev()
doer.DoSomeStuff()
}
During unit testing, the ReadFile operation should be mocked. How can one best achieve this in go test?
Dev struct could instead use FileReader interface, but then struct embedding is no longer used and the syntax in DoSomeStuff becomes less obvious.
If you are using a DI framework such as Dargo you can inject something that implements FileReader into dev. In your main-line code you would bind the normal FileReader, but in your test you can use a mock FileReader. The rest of your code should not know the difference. It would look something like this:
import (
"github.com/jwells131313/dargo/ioc"
"io/ioutil"
"testing"
)
type FileReader interface {
ReadFile(filename string) ([]byte, error)
}
type FileRead struct{}
// ReadFile reads from filename fn using ioutilReadFile
func (fr FileRead) ReadFile(fn string) ([]byte, error) {
return ioutil.ReadFile(fn)
}
type Dev struct {
MyReader FileReader `inject:"FileReader"`
}
/* Not here anymore, but you can implement DargoInitializer
if you need more initialization of Dev
func NewDev() *Dev {
frd := FileRead{}
return &Dev{frd}
}
*/
// Function that does some job
func (dev Dev) DoSomeStuff() {
//...
dev.MyReader.ReadFile("file")
//...
}
var locator ioc.ServiceLocator
func initialize() error {
l, err := ioc.CreateAndBind("Example", func(binder ioc.Binder) error {
binder.Bind("Dev", &Dev{})
binder.Bind("FileReader", &FileRead{})
return nil
})
locator = l
return err
}
func main() {
initialize()
raw, _ := locator.GetDService("Dev")
doer := raw.(*Dev)
doer.DoSomeStuff()
}
// Here is test code
type TestFileRead struct{}
// ReadFile is a mock that just returns a zero-length byte array
func (tfr TestFileRead) ReadFile(fn string) ([]byte, error) {
return []byte{}, nil
}
func TestMe(t *testing.T) {
initialize()
ioc.BindIntoLocator(locator, func(binder ioc.Binder) error {
binder.Bind("FileReader", &TestFileRead{}).Ranked(1)
return nil
})
// Test Me!
}
In the above example the "normal" file reader is injected in the normal code, but in the test there is a TestFileReader with a higher rank. So when you went to get Dev in the test it would be injected with the test one, not the one from the main-line code.
Hope this helps.