unit-testinggomockingclosuresgomock

How to compare/match closures in mocks?


TL;DR: mocked method accepts closure. I wonder how to create custom matcher (https://godoc.org/github.com/golang/mock/gomock#Matcher): closure itself in turn is working with private structure - meaning I can't even call the closure in my test to check it against expectations.


I'm working on a small app using Slack API with help of nlopes/slack (https://github.com/nlopes/slack).

For testing, I'm mocking nlopes/slack with gomock. For that I've created interface

type slackAPI interface {
    OpenConversation(*slack.OpenConversationParameters) (*slack.Channel, bool, bool, error)
    PostMessage(channelID string, options ...slack.MsgOption) (string, string, error)
    GetUserByEmail(email string) (*slack.User, error)
}

I have no problem testing OpenConversation or GetUserByEmail, e.g.

slackAPIClient.
    EXPECT().
    GetUserByEmail("some@email.com").
    Return(slackUserJohndoe, nil).
    Times(1)

Things get more complicated when it comes to PostMessage. In main code the call looks like

_, _, err := slackAPIClient.PostMessage(channel.ID, slack.MsgOptionText(message, false))

And slack.MsgOptionText (from nlopes/slack) is actually returning closure:

func MsgOptionText(text string, escape bool) MsgOption {
    return func(config *sendConfig) error {
        if escape {
            text = slackutilsx.EscapeMessage(text)
        }
        config.values.Add("text", text)
        return nil
    }
}

Since method is accepting closure, I need to create custom gomock matcher (https://godoc.org/github.com/golang/mock/gomock#Matcher). Custom matcher itself is not a problem, it would look something like

type higherOrderFunctionEqMatcher struct {
    x interface{}
}

func (e hofEqMatcher) Matches(x interface{}) bool {
    //return m.x == x
    return true
}

func (e hofEqMatcher) String(x interface{}) string {
    return fmt.Sprintf("is equal %v", e.x)
}

However, since MsgOptionText uses nlopes/slack private structure sendConfig, I wonder how can I even work with that in scope of my test to check equality to expectations.

How should I tackle such problem?


Solution

  • Bearing in mind that

    1. in Golang you can't compare functions
    2. in this precise case I can't do indirect test by calling closure itself (since it's using private 3rd party lib's structure as an argument)

    the solution I've found is to mock slack.MsgOptionText(message, false), which in turn returns closure for PostMessage(channelID string, options ...slack.MsgOption):

    type slackMsgCreator interface {
        MsgOptionText(string, bool) slack.MsgOption
    }
    
    type slackMsgCreatorInst struct{}
    
    func (s slackMsgCreatorInst) MsgOptionText(text string, escape bool) slack.MsgOption {
        return slack.MsgOptionText(text, escape)
    }
    

    ...

    slackMsgCreator.
        EXPECT().
        MsgOptionText("Dear John Doe, message goes here", false).
        Return(slack.MsgOptionText("Dear John Doe, message goes here", false)).
        Times(1)
    

    And, as for PostMessage - as was advised in comments, the only thing that I could check is that closure is not nil:

    slackAPIClient.
        EXPECT().
        PostMessage("ABCDE", Not(Nil())).
        AnyTimes()