セキュリティ系の勉強・その他開発メモとか雑談. Twitter, ブログカテゴリ一覧
本ブログはあくまでセキュリティに関する情報共有の一環として作成したものであり,公開されているシステム等に許可なく実行するなど、違法な行為を助長するものではありません.

scalatest Mockテスト

//

playframeworkでのモックテストを実装することになって色々わからなくて苦しんだのでメモ。

そもそも

DI(依存性の注入)をしっかり意識して実装しないと、ユニットテストが行いにくい(ユニットテストのためにIDを意識すると言っても大丈夫なくらい)

モックオブジェクト

モックオブジェクトを作ることで、テストを行いたいメソッド内の不必要な処理を置き換えることが可能。

テストクラス

ユーザとメールアドレスを紐付けるUserMailLinkerをテストするクラスを作る。MockitoSugerを使用することでMockオブジェクトを作成可能に。

class UserMailLinkerTest extends FlatSpec with Matchers with MockitoSugar

今回はUserMailLinker内に実装した以下のメソッドをテスト(正確にはUserMailLinkerはtraitとなっており、それをextendsする形でUserMailLinkerImplクラスを実装、その中にこのメソッドを定義している)。メソッド内のloginMailDALはデータベースにアクセスする感じのメソッドが格納されている。メソッド外クラス内に変数として定義されている。

val loginMailDAL = new LoginMailDAL() //ちょっと雑に書いたのでここ違うかも。とにかく、変数として実装しておくのがミソ
override def checkEmailExist(email: String) = {
    loginMailDAL.getUserByEmail(email).map(_ match {
      case Some(user) =>{
        logger.info(s"Email:${user.email} is already exist")
        true
      }
      case None => false
    })
  }

このメソッドをテストするには、loginMailDALの結果を明確にこちらで操作できると都合が良い。同時に、テストするときにDBにアクセスするのは良くないし、依存がどーのこーのとID面から見てもよろしくない。そこで、使用するのがモックオブジェクト。UserMailLinkerTest内に以下のように実装する。

  val mockLoginMailDal:LoginMailDAL = mock[LoginMailDAL]
  val linker = new UserMailLinkerImpl(){
       override val loginMail = mockLoginMailDal
 }

これまた少し雑に書いたけれど、こんな感じで、モックオブジェクトを生成してそれでloginMail内の変数を上書きしてしまうという技。後は上書きに使用した、mockLoginMailDal内のメソッドを弄ってやると、うまくテストが可能。loginMailDal内のgetUserByEmail(email:String)を書き換えるには、UserMailLinkerTest内に以下の文を追加。

Mockito.when(mockLoginMailDal.getUserByEmail("email@exist.com")).thenReturn(loginMail) //loginMailはユーザ情報を保持する

これで、先ほど生成したモックオブジェクトが持つgetUserByEmail()は、”email@exist.com”という文字列を受け取ったら他の処理は何もせずに、ただloginMailをリターンするだけのメソッドになる。こうすることで、UserMailLinker内のcheckEmailExist()に実装されているものも上書きされ、メソッドのテストのみに集中できる。

/** 既に登録してあるアドレスならtrueが帰ってくる */
  "checkEmailExist" should "check Email is Already Registered" in {
    val f:Future[Boolean] = linker.checkEmailExist("email@exist.com").run()
    ScalaFutures.whenReady(f){ f=>
      assert(f == true)
    }
  }

ちなみに、例外をthrowするものをテストしたい場合はintercept[T]()を使用する。これを使わないと意図させて起こした例外も、結局ただの実行中に起こったエラーとして扱われてしまう。以下は、メアドとパスワードが違った場合、エラーを投げるgetLoginMailByEmailAndPassword()メソッドをテストしている。

"getLoginMailByEmailAndPassword" should "Error by incorrect information" in{
    intercept[Exception]( linker.getLoginMailByEmailAndPassword("email@nonExist.com", "Password1").run())
  }
||

追記 2/28
上の例外テストはあまりよろしくないかも。
>|scala|
val incorrectE = linker.getLoginMailByEmailAndPassword("email@nonExist.com", "Password1").run()
    ScalaFutures.whenReady(incorrectE.failed){ e => assert(e == 予想されるexception系のもの) }

incorrectEはFuture型を保持するとなり、その中に投げられたエラーが包まれている感じになってる。なので、その中身を取り出してパワーアサートで比較してあげるのがいいはず。