はじめましての方ははじめまして。 Scala エンジニアの Javakky です。
今回は、弊チームで行われた Mockito の導入レポートについて代筆していこうと思います!
Mockito とは?
Mockito は Java 向けのモックフレームワークです。
テスト文脈におけるモックとは、特定のオブジェクトなどの振る舞いを差し替えたダミーのことです。モックを利用することでテスト対象以外の依存をなるべく排してテストができるようになります。
モックフレームワークである Mockito を利用することでテスト中に利用する Java や Scala のオブジェクトの振る舞いを差し替えることができるようになります。
以下が Mockito のページのサンプルを Scala に書き換えたものです。
// モックインスタンス生成 mockedList: List = mock(classOf[List]) // mockedList.get に 0 が渡された場合、 "first" を返すように設定 when(mockedList.get(0)).thenReturn("first") print(mockedList.get(0)) // "first" print(mockedList.get(999)) // null
より具体的な使い道として、 DI するオブジェクトを Mock オブジェクトにすることでテストコードから直接呼び出していないメソッドについても差し替えを行うことができます。
背景
Mockito 導入前の弊チームにもモックという概念はありましたが、 Mock ライブラリを一切利用しておらず、以下のような Mock クラスを全てのトレイトに対して実装していました。
trait HogeRepository { def hoge(): Unit } class HogeRepositoryImpl extends HogeRepository { /* 省略 */ } class HogeRepositoryMock extends HogeRepository { var hogeBody: () => Unit = () => println("mock") def clear(): Unit = hoge = () => println("mock") override def hoge(): Unit = hogeBody() }
しかし、この方法では新しいトレイトやメソッドを実装するために、定型的な変更を大量に施す必要があり、非常に不便でした。これを改善するためにモックフレームワークの導入を検討しました。
その他の候補として、 Scala 向けの Mockito ラッパーである、 Mockito Scala も候補に挙がりました。しかし、内部的に Scala マクロを利用しており Scala 3 系への対応が不透明だったため採用を見送りました。
Mockito の導入方法と使い方
Maven Central で公開されているため、 libraryDependencies
に追加するだけで簡単に使うことができます。
libraryDependencies ++= Seq( "org.mockito" % "mockito-core" % "4.4.0" )
ハマったこと
Mockito はあくまでも Java 向けのフレームワークなので、 Scala で利用すると思わぬハマりどころがありました。
1. implicit パラメータの設定を忘れる
以下のコードの `hoge` のようなメソッドをモックする際に、 `when` メソッド内で `implicit` パラメータの指定を忘れると、エラーが発生して悩むことになります。
class Hoge { def hoge(i: Int)(implicit session: Session): String } val mock = mock(classOf[Hoge]) when(mock.hoge(1)).thenReturn("mock") // mock.hoge(1)(ArgumentMatchers.any()) とするべき
2. 不明な NullPointerException
先ほども少し書いた通り、 Mockito ではモックされていない条件でメソッドが呼び出された場合、常に `null` を返します。その結果、普段の Scala 開発ではほぼ発生しない `NullPointerException` が予期せぬ位置で発生することになります。
コーディング規約と Scalafix ルールの作成
Mockito には When/Then と Do/When という2つの記法があります。
when(mock.hoge(1)).thenReturn("mock") doReturn("mock").when(mock).hoge(1)
これはほとんど同じ挙動になるのですが、よく見てみると hoge(1)
の位置が違います。
また、 void
(Scala では Unit
) が返り値の型に指定されるようなメソッドの mock には doNothing
というメソッドしか用意されておらず、 When/Then 構文では実装することができません。
この仕様が原因で実装時のケアレスミスが多発したため、弊チームでは Do/When 構文を使うというコーディング規約が作られました。
ただ、規約が決まった時点で When/Then 構文があちこちに使われていたため、書き換え用の scalafix ルールを作成して対応しました。
github.com github.com (scalafix のルール作成については過去記事にて紹介しています) inside.pixiv.blog
おわりに
今回は Scala プロジェクトで Java のモックフレームワーク Mockito を導入・運用したレポートをお送りしました。
元々手動でモッククラスを作っていた自分からすると、開発の大きなストレスから解放されて晴れやかな気持ちになることができました!
ぜひみなさんのテスト環境にも導入を検討してみてください!