Проверка вызовов метода фиктивного объекта с аргументами по умолчанию

Предположим, у меня есть этот класс:

class Defaults {
  def doSomething(regular: String, default: Option[String] = None) = {
    println(s"Doing something: $regular, $default")
  }
}

Я хочу проверить, что какой-то другой класс вызывает метод doSomething() для экземпляра Defaults без передачи второго аргумента:

defaults.doSomething("abcd")  // second argument is None implicitly

Однако насмешка над классом Defaults работает некорректно. Поскольку значения по умолчанию для аргументов метода скомпилированы как скрытые методы в том же классе, mock[Defaults] возвращает объект, в котором эти скрытые методы возвращают null вместо None, поэтому этот тест не пройден:

class Test extends FreeSpec with ShouldMatchers with MockitoSugar {
  "Defaults" - {
    "should be called with default argument" in {
      val d = mock[Defaults]

      d.doSomething("abcd")

      verify(d).doSomething("abcd", None)
    }
  }
}

Ошибка:

Argument(s) are different! Wanted:
defaults.doSomething("abcd", None);
-> at defaults.Test$$anonfun$1$$anonfun$apply$mcV$sp$1.apply$mcV$sp(Test.scala:14)
Actual invocation has different arguments:
defaults.doSomething("abcd", null);
-> at defaults.Test$$anonfun$1$$anonfun$apply$mcV$sp$1.apply$mcV$sp(Test.scala:12)

Причина этого ясна, но есть ли разумный обходной путь? Единственное, что я вижу, это использовать spy() вместо mock(), но мой фиктивный класс содержит множество методов, которые в этом случае мне придется явно имитировать, а я этого не хочу.


person Vladimir Matveev    schedule 04.08.2014    source источник
comment
К сожалению, если вы используете сопоставители аргументов, все аргументы должны предоставляться сопоставителями docs.mockito.googlecode.com/hg/org/mockito/Mockito.html#3   -  person Johnny Everson    schedule 04.08.2014
comment
@JhonnyEverson, я никогда не говорил, что использую сопоставители. Из моих примеров видно, что не используется ни один сопоставитель.   -  person Vladimir Matveev    schedule 04.08.2014
comment
ага, извините. Возможно, вы могли бы использовать некоторые неявные параметры вместо параметров по умолчанию, но я не уверен, что это сработает. Просто идея.   -  person Johnny Everson    schedule 04.08.2014
comment
Я не уверен, что Mockito может это сделать. В вашем случае d - это просто стандартный прокси-сервер Java, который внутренне вызывает реальный экземпляр Defaults. Поскольку в Java нет понятия парадигмы Some/None, она будет использовать null при динамической привязке аргументов - для Java ваш метод doSomething() является обычным методом с двумя параметрами, поэтому отражающий вызов должен будет заполнить список аргументов чем-то , что в вашем случае null. Кстати, вы пытались решить эту проблему с помощью родного ScalaMock?   -  person Pavel Lechev    schedule 12.09.2016


Ответы (1)


Это связано с тем, как компилятор Scala реализует это как класс Java, помните, что Scala работает на JVM, поэтому все должно быть преобразовано во что-то похожее на Java.

В данном конкретном случае компилятор создает серию скрытых методов, которые будут называться примерно так: methodName$default$number, где number — позиция аргумента. представляет этот метод, то компилятор будет проверять каждый раз, когда мы вызываем этот метод, и если мы не предоставим значение для такого параметра, он вставит на его место вызов метода $default$, пример « скомпилированная версия будет примерно такой (обратите внимание, что это не совсем то, что делает компилятор, но это работает в образовательных целях)

class Foo {
   def bar(noDefault: String, default: String = "default value"): String
}
val aMock = mock[Foo]

aMock.bar("I'm not gonna pass the second argument")

Последняя строка будет скомпилирована как

aMock.bar("I'm not gonna pass the second argument", aMock.bar$default$1)

Теперь, поскольку мы вызываем bar$default$1 в макете, а поведение Mockito по умолчанию состоит в том, чтобы возвращать null для всего, что не было заглушено, то в итоге выполняется что-то вроде

aMock.iHaveSomeDefaultArguments("I'm not gonna pass the second argument", null)

Именно об этом и говорит ошибка…

Чтобы решить эту проблему, необходимо внести некоторые изменения, чтобы mockito фактически вызывал настоящие методы $default$, и поэтому замена выполнялась правильно.

Эта работа была выполнена в mockito-scala, поэтому, перейдя на эту библиотеку, вы получите решение этой и многих других проблем, которые можно найти при использовании mockito в Scala

Отказ от ответственности: я разработчик mockito-scala

person Bruno    schedule 30.10.2018