如何在Mockito和Scala中使用隐式匹配器存根方法调用

14

我的应用程序代码使用 AService

trait AService {
    def registerNewUser (username: String)(implicit tenant: Tenant): Future[Response]
}

注册新用户。租户类是一个简单的case类:

case class Tenant(val vstNumber:String, val divisionNumber:String) 

Trait AServiceMock使用AService的模拟版本来模仿注册逻辑。

trait AServiceMock {
  def registrationService = {
    val service = mock[AService]
    service.registerNewUser(anyString) returns Future(fixedResponse)
    service
  }
}
每当在 AService 上调用 registerNewUser 时,响应将是“fixedResponse”(在其他地方定义)。
我的问题是,我如何像使用 anyString 一样定义隐式租户参数的 mockito 匹配器?
顺便说一下,我正在使用 Mockito、Specs2(和 Play2)。

1
一个猜测:implicit def tenantMatcher = any[Tenant] 怎么样? - Eric
@Eric 猜得真好!我喝了两杯咖啡才看到下面的内容 ;) - simou
2个回答

21
有时候你必须先在 Stack Overflow 上发布问题,才能得到显然的答案(嗯):

有时候你必须要先在 Stack Overflow 上发帖,才能得到完全明显的答案(duhh):

service.registerNewUser(anyString)(any[Tenant]) returns Future(fixedResponse)

1
这是对@simou答案的补充。目前我认为应该这样做,但我认为了解为什么应避免@Enrik提出的替代解决方案很有趣,因为在某些情况下它可能会在运行时失败并显示加密错误。 如果您想要在存根中对隐式参数进行精确匹配,您可以安全地将其添加到范围中。
trait AServiceMock {
  implicit val expectedTenant: Tenant = Tenant("some expected parameter")
  def registrationService = {
    val service = mock[AService]
    service.registerNewUser(anyString) returns Future(fixedResponse)
    service
  }
}

这样做是可行的,但前提是 service.registerNewUser 函数需要接收与期望的租户(由隐式值 expectedTenant 提供)完全相同的租户参数。

另一方面,以下风格的任何内容都不会可靠地起作用:

implicit val expectedTenant1: Tenant = any[Tenant]

implicit def expectedTenant2: Tenant = any[Tenant]

implicit def expectedTenant3: Tenant = eqTo(someTenant)

理性与Mockito如何创建其参数匹配器有关。
当您编写myFunction(*,12)returns“abc”时,Mockito实际上使用了一个宏:
  1. 添加代码以初始化列表,其中参数匹配器可以注册
  2. 如果需要,将所有不是匹配器的参数包装在匹配器中。
  3. 添加代码以检索为此函数声明的匹配器列表。
在预期的expectedTenant2或expectedTenant3的情况下,可能会在评估该函数时注册第一个参数匹配器。但是,该宏将不会看到此函数正在注册macther。它只会考虑此函数声明的返回类型,因此可能决定将返回的值包装在第二个匹配器中。
因此,在实践中,如果您有以下代码:
trait AServiceMock {
  implicit def expectedTenant(): Tenant = any[Tenant]
  def registrationService = {
    val service = mock[AService]
    service.registerNewUser(anyString) returns Future(fixedResponse)
    service
  }
}

你期望在应用隐式后它是这样的:
trait AServiceMock {
 
  def registrationService = {
    val service = mock[AService]
    service.registerNewUser(anyString)(any[Tenant]) returns Future(fixedResponse)
    service
  }
}

但实际上,Mockito宏将使其成为类似于以下内容的东西:
trait AServiceMock {
 
  def registrationService = {
    val service = mock[AService]
    // In practice the macro use DefaultMatcher and not eqTo but that do not change much for the matter we discuss.
    service.registerNewUser(anyString)(eqTo(any[Tenant])) returns Future(fixedResponse)
    service
  }
}

现在你在stub的隐式参数中声明了两个matcher。当Mockito检索为registerNewUser声明的匹配器列表时,它将看到其中三个,并认为你正在尝试为一个只需要两个参数的函数注册具有三个参数的存根,并记录:

Invalid use of argument matchers!
2 matchers expected, 3 recorded:

我还不确定为什么它在某些情况下仍然有效,我的假说是:

  • 也许宏有时会在某些情况下决定不需要匹配器,并且不会在隐式函数返回的值周围包装额外的匹配器。
  • 也许启用了某些宽容选项,mockito会忽略额外的匹配器。即使是这种情况,额外的匹配器也可能会搅乱你存根的参数顺序。
  • 还可能在某些情况下,scala编译器内联隐式定义,这将允许宏看到使用了匹配器的情况。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接