unit-testing - 如何比较/匹配模拟中的闭包?

标签 unit-testing go mocking closures gomock

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

在 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 私有(private)结构 sendConfig,我想知道如何在我的测试范围内使用它来检查与预期的相等性。

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

最佳答案

牢记这一点

  • 在 Golang 中你无法比较函数
  • 在这种精确的情况下,我不能通过调用闭包本身来进行间接测试(因为它使用私有(private) 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 - 正如评论中所建议的那样,我唯一可以检查的是闭包不是零:
    slackAPIClient.
        EXPECT().
        PostMessage("ABCDE", Not(Nil())).
        AnyTimes()
    

    关于unit-testing - 如何比较/匹配模拟中的闭包?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60466503/

    相关文章:

    c++ - 如何使这些类对单元测试更友好?

    java - 如何对自定义 Wicket 组件进行单元测试

    c# - 我如何使用 Caller Info 属性进行 TDD?

    xml - 使用 Go 生成 XML 文件时,如何创建 doctype 声明?

    testing - 当一个具体方法的签名引用另一个具体类型而不是它的接口(interface)时,我如何模拟多个类型?

    python - 如何模拟导入以阅读文档?

    visual-studio-2008 - Visual Studio 单元测试中的 HttpRuntime

    使用 Go 进行 JSON 解码失败

    http - 什么是可能的 golang http 响应错误?

    c++ - 如何为按地址传递参数的 Windows API 调用编写测试替身