specs2+mockitoで引数あり関数のstub(mock)を作成する方法まとめ
前回の記事でspecs2とmockitoでmockを作ってテストする方法を解説しました。
specs2のmock機能(stub機能)とtrait+overrideを使ってテスタビリティを上げる
前回の記事を読むだけである程度テストを書けるようになると思いますが、今回はもう少し解説して行きます。
今回はmockを作る部分のみ解説しますので、より実践的なテストコードを書く方法については前回の記事を参照してください。
引数あり関数のmock
any***を使えばOKです。
例えば
import org.specs2.mock.Mockito
import org.specs2.mutable.Specification
trait SampleClass {
def add(a: Int, b: Int): Int = a + b
}
object SampleClass extends SampleClass
class SampleSpec extends Specification with Mockito {
"Mock" should {
"mock" in {
val m = mock[SampleClass]
m.add(anyInt, anyInt) returns 10
m.add(1, 1) must be equalTo 10
}
}
}
というようにすればOKです。引数がある場合は上記のようにanyIntなどを使います。
勿論実際に呼び出されるときの引数を固定したり、引数の値によって挙動を変えたい場合はanyIntなどではなく、実際の値を使います。
AnyValでない引数ありの関数のmock
上記の場合はanyIntを使いましたが、引数が独自クラスなどの場合はanyを使います。
import org.specs2.mock.Mockito
import org.specs2.mutable.Specification
case class MyEntity(id: Int)
trait SampleClass {
def extractId(obj: MyEntity): Int = obj.id
}
object SampleClass extends SampleClass
class SampleSpec extends Specification with Mockito {
"Mock" should {
"mock" in {
val m = mock[SampleClass]
m.extractId(any) returns 100
m.extractId(MyEntity(0)) must be equalTo 100
}
}
}
簡単ですね。
関数にimplicit parameterがある場合は要注意
以下のような感じの関数があるとします。
def getString(str: String)(implicit prefix: String): String = prefix + str
こういう関数でもmockの作り方は変わらないのですが・・・implicit parameterの存在を忘れていると悲惨なことになります。(自分はこのせいで2時間くらいハマってました・・・)
では早速**「ダメな例」**を書いてみます。
import org.specs2.mock.Mockito
import org.specs2.mutable.Specification
trait SampleClass {
def getString(str: String)(implicit prefix: String): String = prefix + str
}
object SampleClass extends SampleClass
class SampleSpec extends Specification with Mockito {
"Mock" should {
"mock" in {
implicit val prefix = "[prefix]"
val m = mock[SampleClass]
m.getString(any) returns "hoge"
m.getString("aaa") must be equalTo "hoge"
}
}
}
これはコンパイル成功しますが、実行時にエラーが出ます。
以下がエラー文です。
Invalid use of argument matchers!
2 matchers expected, 1 recorded:
-> at org.specs2.mock.mockito.MockitoMatchers$class.any(MockitoMatchers.scala:24)
This exception may occur if matchers are combined with raw values:
//incorrect:
someMethod(anyObject(), "raw String");
When using matchers, all arguments have to be provided by matchers.
For example:
//correct:
someMethod(anyObject(), eq("String by matcher"));
For more info see javadoc for Matchers class.
org.mockito.exceptions.misusing.InvalidUseOfMatchersException:
Invalid use of argument matchers!
2 matchers expected, 1 recorded:
-> at org.specs2.mock.mockito.MockitoMatchers$class.any(MockitoMatchers.scala:24)
This exception may occur if matchers are combined with raw values:
//incorrect:
someMethod(anyObject(), "raw String");
When using matchers, all arguments have to be provided by matchers.
For example:
//correct:
someMethod(anyObject(), eq("String by matcher"));
For more info see javadoc for Matchers class.
at specs2.mock.SampleSpec$$anonfun$1$$anonfun$apply$1$$anonfun$apply$2.apply(SampleSpec.scala:19)
at specs2.mock.SampleSpec$$anonfun$1$$anonfun$apply$1$$anonfun$apply$2.apply(SampleSpec.scala:19)
at specs2.mock.SampleSpec$$anonfun$1$$anonfun$apply$1.apply(SampleSpec.scala:19)
at specs2.mock.SampleSpec$$anonfun$1$$anonfun$apply$1.apply(SampleSpec.scala:14)
anyObject()とかはMockito(Java)のメソッドです。さっぱり意味が分からないしanyの使い方もあってるのに、という感じですが・・・。
これはどういうことかというと、implicit parameter部分のanyが抜けているから発生しています。という訳で、m.getString(any)としている部分をm.getString(any)(any)とすると正しく動きます。
「成功例」
import org.specs2.mock.Mockito
import org.specs2.mutable.Specification
trait SampleClass {
def getString(str: String)(implicit prefix: String): String = prefix + str
}
object SampleClass extends SampleClass
class SampleSpec extends Specification with Mockito {
"Mock" should {
"mock" in {
implicit val prefix = "[prefix]"
val m = mock[SampleClass]
m.getString(any)(any) returns "hoge"
m.getString("aaa") must be equalTo "hoge"
}
}
}
まとめ
- 引数ありの場合はanyIntやanyを使う
- 独自クラスでもanyを使えばOK
- implicit parameterの存在を忘れると変なエラーが実行時に出る
実は機能的にはまだまだ大量にあって、ここまででmockの全体の10%程度しか解説していません。詳しくは公式をご覧下さい。
http://etorreborre.github.io/specs2/guide/org.specs2.guide.Matchers.html