【问题标题】:How to compare/match closures in mocks?如何比较/匹配模拟中的闭包?
【发布时间】:2020-06-13 10:19:33
【问题描述】:

TL;DR:模拟方法接受闭包。我想知道如何创建自定义匹配器 (https://godoc.org/github.com/golang/mock/gomock#Matcher):闭包本身又使用私有结构 - 这意味着我什至无法在测试中调用闭包来检查它是否符合预期。


我正在 nlopes/slack (https://github.com/nlopes/slack) 的帮助下使用 Slack API 开发一个小型应用程序。

为了测试,我用 gomock 模拟 nlopes/slack。为此我创建了界面

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)
}

我在测试 OpenConversation 或 GetUserByEmail 时没有问题,例如

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

对于 PostMessage,事情变得更加复杂。在主代码中,调用看起来像

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

而 slack.MsgOptionText(来自 nlopes/slack)实际上正在返回闭包:

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
    }
}

由于方法接受闭包,我需要创建自定义 gomock 匹配器 (https://godoc.org/github.com/golang/mock/gomock#Matcher)。自定义匹配器本身不是问题,它看起来像

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)
}

但是,由于 MsgOptionText 使用 nlopes/slack 私有结构 sendConfig,我想知道如何在我的测试范围内使用它来检查预期是否相等。

我应该如何解决这样的问题?

【问题讨论】:

  • 不确定我是否完全理解您要执行的操作,但您知道,在 Go 中,无法比较函数,即您无法检查传入的闭包是否与预期匹配关闭,这是不可能的,不是在 Go 中。
  • 我正在尝试为我的代码创建单元测试。为此,我在嘲笑 Slack API(它是 nlopes/slack 实现)——所以单元测试不会有任何真正的调用。然后我为模拟函数设定期望。当模拟函数接受常规变量、结构等时,这不是问题。您只需比较它们。模拟函数接受闭包似乎是一个问题(这是 nlopes/slack 模块的设计,不是我的选择)。因为你无法比较 Go 中的闭包。所以,我想弄清楚如何测试这样的功能:)
  • 我认为在这种情况下你能做的最好的就是检查传入的闭包不是nil,就是这样。
  • 别再嘲讽了,写些普通的赝品吧。这更理智。
  • 我的应用程序正在请求 JIRA 获取一些信息,然后在 Slack 中戳相应的人。对于 JIRA 部分的测试 - 普通的假货可以完美地工作。但是对于 Slack 部分的测试——为了测试我的应用程序,我确实需要使用模拟来验证行为(基本上,我的应用程序做了哪些 Slack 调用)。对吗?

标签: unit-testing go mocking closures gomock


【解决方案1】:

记住这一点

  1. 在 Golang 中,您无法比较函数
  2. 在这种精确的情况下,我无法通过调用闭包本身来进行间接测试(因为它使用私有 3rd 方库的结构作为参数)

我找到的解决方案是模拟 slack.MsgOptionText(message, false),然后返回 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)

而且,至于 PostMessage - 正如 cmets 中所建议的那样,我唯一可以检查的是闭包不是 nil:

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

【讨论】:

    猜你喜欢
    • 2022-08-06
    • 1970-01-01
    • 2018-11-04
    • 2013-08-27
    • 1970-01-01
    • 2015-06-15
    • 1970-01-01
    • 1970-01-01
    • 2015-01-23
    相关资源
    最近更新 更多