Как заглушить вызов метода с неявным сопоставлением в Mockito и Scala

В моем коде приложения используется AService

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

для регистрации нового пользователя. Класс Tenant - это простой класс 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
  }
}

Iow всякий раз, когда registerNewUser вызывается в AService, ответ будет «fixedResponse» (определено в другом месте).

Мой вопрос: как мне определить неявный параметр-арендатор как сопоставление mockito, например anyString?

Кстати. Я использую Mockito со Specs2 (и Play2)


person simou    schedule 26.05.2015    source источник
comment
Дикое предположение: а что насчет implicit def tenantMatcher = any[Tenant]?   -  person Eric    schedule 26.05.2015
comment
@ Эрик прекрасное предположение! Мне потребовалось две чашки кофе, см. Ниже;)   -  person simou    schedule 26.05.2015


Ответы (2)


Иногда вам нужно сначала опубликовать сообщение на SO, чтобы получить совершенно очевидный ответ (черт возьми):

service.registerNewUser(anyString)(any[Tenant]) returns Future(fixedResponse)
person simou    schedule 26.05.2015

Это дополнение к ответу @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. добавьте код для получения списка сопоставителей, объявленных для этой функции.

В случае ожидаемогоТенант2 или ожидаемогоТенант3 может добавляться то, что сопоставление первого аргумента будет зарегистрировано при оценке функции. Но макрос не увидит, что эта функция регистрирует 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, это позволит макросу увидеть, что использовалось сопоставление.
person Thibault Urien    schedule 17.05.2021