ScalaTestでは以前からMockitoSugarというトレイトが提供されており、ScalaからMockitoをちょっとだけ便利に使用することができるようになっていたのですが、元々それほど大した機能もなかった上に現在は別ライブラリに切り出されてしまったこともあり、使うモチベーションがだいぶ薄れてしまったのではないかと思います。実際のところ、ScalaTestのMockitoSugarを使わずに直接Mockitoを使っても大差ないのですが、やはりScalaからMockitoを使っていると不便に感じる点があったりします。
Mockito本家ではmockito-scalaというScala向けのライブラリが開発されており、こちらはScala向けにかなり作り込まれているようなので軽く試してみました。
このライブラリを使うにはbuild.sbtに以下の依存関係を追加します。Mockito本体はmockito-scalaの依存関係で引っ張ってくるので明示的に追加する必要はありません。
libraryDependencies ++= Seq( "org.mockito" %% "mockito-scala" % "1.16.0" % "test" )
基本的な使い方はMockitoSugar、ArgumentMatchersSugarというトレイトをテストケースにミックスインします。
import org.scalatest.funsuite.AnyFunSuite import org.mockito.{ ArgumentMatchersSugar, MockitoSugar } class MyTest extends AnyFunSuite with MockitoSugar with ArgumentMatchersSugar { ... }
もしくはmockito-scalaではトレイトに対応するシングルトンオブジェクトも用意されており、トレイトをミックスインする代わりにシングルトンオブジェクトのメンバをインポートすることで同じように使うことができます。
import org.scalatest.funsuite.AnyFunSuite import org.mockito.ArgumentMatchersSugar._ import org.mockito.MockitoSugar._ class MyTest extends AnyFunSuite { ... }
mockito-scalaでは以下のようにMockitoをScalaから使う際に便利な様々な機能が提供されています。
- ScalaTestの
MockitoSugarと同じようにmock[T]でモックを生成できる - 同様に例外スローや引数のキャプチャも
doThrow[T]やCaptor[T]のように記述できる mock[MyClass with MyTrait]のようにモックの生成時にトレイトをミックスインできる- Mockitoのオーバーロードされた可変長引数メソッドの呼び出しが簡単にできる(
doReturn(value, Nil: _*)をJava同様doReturn(value)と記述できる) spyLambdaで関数のspyが可能verifyでデフォルト引数や名前渡し(遅延評価)の引数も適切に扱うことができるeqがScalaTestのMatcherと衝突してしまう問題が解消されている(予めeqToが用意されている)- Matcherの型変数や括弧を省略可能(
any[String]やanyStringの代わりにanyと記述できる) - null撲滅のためにw Null matcherに警告が出る
- 引数なしの関数のために
function0matcherが用意されている - Value Classのために
eqToValmatcherが用意されている ArgumentCaptorの代わりにシンプルかつValue ClassをサポートしたArgCaptorが提供されている- MockitoのStrict Stubsのサポート(Idiomatic Syntaxではデフォルト)
- ScalaTest用の便利トレイト(
MockitoFixture、ResetMocksAfterEachTestなど) - Answerを関数で記述できるようになっており、
Invocationから引数を抽出する代わりに関数の引数として受け取ることができる - Idiomatic SyntaxやExpect DSLというマクロを活用したDSLが提供されている
最後のIdiomatic Syntaxですが、たとえば通常のMockitoを使用した次のようなコードがあるとします。
when(aMock.bar) thenReturn "mocked!" when(aMock.baz(any)) thenReturn "mocked!" verify(aMock, times(6)).bar verify(aMock, atLeast(6)).baz(any)
Idiomatic Syntaxではこのコードを次のような感じで記述することができるというものです。語順が異なるだけでなく、anyの代わりに*が使えたりします。
aMock.bar returns "mocked!" aMock.baz(*) returns "mocked!" aMock.bar wasCalled 6.times aMock.baz(*) wasCalled atLeast(6.times)
Expect DSLはこのIdiomatic Syntaxをさらに変形させたようなもので、Idiomatic Syntaxで次のように記述するところを
aMock.bar wasCalled 6.times aMock.baz(*) wasCalled atLeast(6.times)
expectから始まるDSLで置き換えることができます。
expect exactly 6.calls to aMock.bar expect atLeast 6.calls to aMock.baz(*)
先頭にexpectが来るのでverifyしていることがわかりやすいということのようですが、それなら元のMockitoのverifyでいいのではという気がしなくも…。
その他、機能の細かい部分についてはmockito-scalaのREADMEや、mockito-scalaの作者であるBruno Bonannoさんのブログでも開設されています。
mockito-scalaはかなり機能豊富でScalaからMockitoを使う際に便利になっている部分も多いのですが、Idiomatic SyntaxやExpect DSLは若干やりすぎ感がなくもない感じがします。Mockitoというより別のモックライブラリを使っている感じというか…。とはいえMockitoの知識なしで使えるかというとそれも微妙な感じなので、結局学習コストが二重にかかるだけなのではという懸念があります。また、マクロが活用されているということもあり、もし今後万が一mockito-scalaのメンテが止まってしまった場合の対応もなかなか大変そうな予感がします。
それ以外の部分については、特にValue Classやデフォルト引数、名前渡し引数のサポートやAnswerを関数と書けるようになっているあたりはなかなかポイント高いのではないかと思うので、独自DSLを使わず基本機能だけ使うのもありなのではないかと思いました。