名前渡しするメソッドをもつやつのモックを定義しようとしたら地獄だった。
モック化する奴
trait Foo {
def nyan(s: => String, repeat: Long)
}
このモックを verify
しようとするとこんな風に書かないといけなくなる:
地獄.scala
import org.mockito.ArgumentMatcher
import org.scalatest.FunSuite
import org.scalatest.mock.MockitoSugar
import org.mockito.Mockito._
import org.mockito.Matchers._
import org.mockito.Matchers
class MockingWithByName extends FunSuite with MockitoSugar {
test("mocking with a by-name param and AnyRef param") {
val foo = mock[Foo]
val sound = "nyaaaaan"
val repeat = 1111L
//
foo.nyan(sound, repeat)
//
// ダメ
//verify(foo, times(1)).nyan(sound, repeat)
// => Argument(s) are different! Wanted:
// 問答無用で Function0 にラップされて nyan に渡されるので、
// 実際に呼ばれた時と、verify 時で、違う Function0 のインスタンスになってしまうため。
// じゃあこうすると ...
classOf[Foo].getMethod("nyan", classOf[Function0[_]], classOf[Long])
.invoke(
verify(foo),
argThat(new ArgumentMatcher[Function0[_]] {
override def matches(argument: Any): Boolean =
argument.asInstanceOf[Function0[_]].apply() == sound
}),
//Matchers.eq(repeat) // ダメ
// ↑ invoke の第二引数以降は AnyRef 期待されてるんだけど、
// nyan 自体は scala.Long (AnyRef でないクラス)を期待してるので無理
Matchers.eq(new java.lang.Long(repeat)) // こうする必要がある
)
// 完
}
}
// モックにするやつ
trait Foo {
def nyan(s: => String, repeat: Long)
}
スタブ作ったり、when
で返り値の定義をする時にも同様の書き方が必要になります。
つらすぎる。今回使った Mockito 以外にも Java 向けのモックフレームワークを薄くラップしてる系のものは同じ悩みを抱えるはずなんですが、もっとスマートに書かける方法/モックフレームワークないの。