Mocking in Go Part 2
In a previous post, I talked about Gomock. I want to spend a bit more time on it.
As I’ve mentioned, setting up Gomock requires
- Download mockgen
go get github.com/golang/mock/mockgen@latest
- Edit your go.mod
module jcheng.org
go 1.13
require (
github.com/golang/mock v1.4.0
)
- Add the go:generate incantation to your code
//go:generate mockgen -source $GOFILE -destination mock_$GOFILE -package $GOPACKAGE
package pastself
import (
"time"
)
...
- Use mocks in your test case. In this example, I used a callback function to match on a nested property.
type fnmatcher struct {
d func(x interface{}) bool
desc string
}
func (self *fnmatcher) Matches(x interface{}) bool {
return self.d(x)
}
func (self *fnmatcher) String() string {
return self.desc
}
func On(desc string, d func(x interface{}) bool) mock.Matcher {
return &fnmatcher{d: d, desc: desc}
}
// TestSendOverdueMessages_ok is an example of using matching using a callback function
func TestSendOverdueMessages_ok(t *testing.T) {
ctrl := mock.NewController(t)
defer ctrl.Finish()
mockUserRepo := NewMockUserRepository(ctrl)
mockMessageRepo := NewMockMessageRepository(ctrl)
mockMessageSender := NewMockMessageSender(ctrl)
log, _ := NewBufferLog()
r := NewPastSelfService(
mockMessageRepo,
mockMessageSender,
mockUserRepo,
log,
)
senderUserDDB_1 := &UserDDB{UserID: "sender1"}
senderUserDDB_2 := &UserDDB{UserID: "sender2"}
recipientUserDDB_2 := &UserDDB{
UserID: "recipient2",
Profile: UserProfileDDB{
PreferredEmail: "recipient2@example.com",
},
}
resultSet := &ResultSet{
Items: []Message{
{
ID: 1,
SenderID: "sender1",
Body: "body1",
Recipients: []string{"email://recipient1@example.com", "recipient2"},
Headers: []Header{
{Name: "x-header-key-1", Value: "value-1"},
{Name: "x-header-key-2", Value: "value-2"},
},
},
{
ID: 2,
SenderID: "sender2",
Body: "body2",
},
},
}
mockUserRepo.EXPECT().GetUser("sender1").Return(senderUserDDB_1, nil)
mockUserRepo.EXPECT().GetUser("sender2").Return(senderUserDDB_2, nil)
mockUserRepo.EXPECT().GetUser("recipient2").Return(recipientUserDDB_2, nil)
mockMessageRepo.EXPECT().FindOverdue(mock.Any(), 0, "").Return(resultSet, nil)
matchFrom := On("user.UserID==sender1", func(x interface{}) bool {
if y, ok := x.(*UserDDB); ok {
return y.UserID == "sender1"
}
return false
})
matchTo := On("[]Recipient{recipient1@example.com,recipient2@example.com}", func(x interface{}) bool {
if y, ok := x.([]Recipient); ok {
return len(y) == 2 &&
y[0].Email == "recipient1@example.com" &&
y[1].Email == "recipient2@example.com"
}
return false
})
matchOutMessage := On("OutboundMessage.Body==Body1", func(x interface{}) bool {
if y, ok := x.(OutboundMessage); ok {
return y.Body == "body1"
}
return false
})
mockMessageSender.EXPECT().Send(matchFrom, matchTo, matchOutMessage).Return(nil).MaxTimes(1)
r.SendOverdueMessages()
}
One thing nice about GoMock is that it generates statically typed method names for the EXPECT()
statements, so the
compiler can check that you’re using the correct method names. Neat.