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

时间:2015-05-26 07:35:04

标签: scala mockito implicit matcher specs2

我的应用程序代码使用AService

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

注册新用户。 Class Tenant是一个简单的案例类:

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

Iow每当在AService上调用registerNewUser时,响应将是" fixedResponse" (在别处定义)。

我的问题是,如何将隐式租户参数定义为像anyString一样的mockito匹配器?

顺便说一句。我使用Mockito和Specs2(和Play2)

2 个答案:

答案 0 :(得分:15)

有时你必须首先发布SO才能得出完全明显的答案(duhh):

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

答案 1 :(得分:0)

这是对@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
  }
}

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

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

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

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