Playframework 2.4 ,Guice,squeryl でテスト

Playframework2.4がリリースされた。
目玉はDIの模様。ドキュメントを確認すると、googleGuiceを利用とのこと。
Guiceについては、2.1の時から利用を推奨されていたので、特に驚きはない。
大きな変化点は、Guice.createInjectorで呼び出していたModule(traitへの注入の指示)が、conf/application.confで指定可能になった事、アノテーションでの注入、
routeファイルでのcontrollerの注入といった感じ。
他にもeager bindingsとかあるが、詳しくはこちら
DIといえばテストとは切り離せない。今まで記事を流し読みしていただけなので、今回は実装してみることにした。
まずはmodel層のテスト。ORMには、squerylを利用。
modelクラス

package models
import org.squeryl.KeyedEntity
import org.squeryl._
import org.squeryl.dsl._
import org.squeryl.PrimitiveTypeMode._

case class Part(name: String, price: Long, projectId: Long) extends KeyedEntity[Long] {
  val id:Long = 0
  lazy val parts = RuntimeDiSampleDb.partRelations.left(this)
}

case class PartRelation(val parentId: Long, childId: Long, var quantity: Long, var relationKet: String, var delDcId: Long, var addDcId: Long, var dcSeq: Long) extends KeyedEntity[CompositeKey2[Long, Long]] {
  def id = compositeKey(parentId, childId)
  lazy val parent:ManyToOne[Part] = RuntimeDiSampleDb.relationParentParts.right(this)
  lazy val child:ManyToOne[Part] = RuntimeDiSampleDb.relationChildParts.right(this)

}

object RuntimeDiSampleDb extends Schema {
  val parts = table[Part]
  val relations = table[PartRelation]

  val partRelations = manyToManyRelation(parts, parts).via[PartRelation]((pp,cp,pr) => (pp.id === pr.parentId, cp.id === pr.childId))
  val relationParentParts = oneToManyRelation(parts, relations).via((p, rl) => rl.parentId === p.id)
  val relationChildParts = oneToManyRelation(parts, relations).via((p, rl) => rl.childId === p.id)
}

Partクラスのテストコード

package models
import play.api.test._
import play.api.test.Helpers._
import org.junit.runner._
import org.specs2.mutable.Specification
import org.specs2.runner._
import org.squeryl._
import org.squeryl.PrimitiveTypeMode._

@RunWith(classOf[JUnitRunner])
class PartSpec extends Specification {
  "Part" should {
    "create new Part" in {
      running(FakeApplication()) {
        transaction {
          val parts = RuntimeDiSampleDb.parts.where { p => p.id ===31 }
          parts.head must not be null
          parts.head.name must beEqualTo("Part10")
        }
      }
    }
  }
}

DBの内容

MariaDB [playdb]> select * from Part where id in (31,8);
+--------+-----------+----+-------+
| name   | projectId | id | price |
+--------+-----------+----+-------+
| Part3  |         1 |  8 |     0 |
| Part10 |        16 | 31 |     1 |
+--------+-----------+----+-------+
2 rows in set (0.00 sec)

Eclipseでテストを実行すればグリーンバー

ここまでは、DIとはまったく無関係な世界。
ではこのpartをcontrollerから呼び出すこともできるのだが、
クラス間(Model vs Controller)の依存度を下げるため、Dao(Data Access Object)を導入
Daoをtrait(interface)と実装(DaoImpl)を分割。
Daoのインターフェースと実装のソースコード

package daos
import daos.impls.PartDaoImpl
import org.squeryl._
import models._
import com.google.inject._

trait PartDao {
  def findByName(name: String):Query[Part]
}
package daos.impls

import daos._
import models._
import org.squeryl._
import org.squeryl.PrimitiveTypeMode._
class PartDaoImpl extends PartDao{
  def findByName(name: String):Query[Part] = {
    RuntimeDiSampleDb.parts.where(m => m.name like name + "%" )
  }
}

Daoのテストコード
GuiceApplicationBuilderクラスで、インターフェースに実装を注入。

package daos
import models._
import daos.impls._
import org.specs2.mutable._
import org.specs2.mock.Mockito
import play.api.mvc._
import play.api.test._
import play.api.test.Helpers._
import org.squeryl.PrimitiveTypeMode._
import org.specs2.runner._
import org.junit.runner._
import play.api.inject.guice._
import play.api.inject.BuiltinModule
import play.api.inject.bind

@RunWith(classOf[JUnitRunner])
class PartDaoSpec extends PlaySpecification{
  "PartDao" should {
    "dao is" in {
      running(FakeApplication()) {
        transaction {
          val injector = new GuiceApplicationBuilder().load(new BuiltinModule, 
            bind[PartDao].to[PartDaoImpl]).injector
          val partDao = injector.instanceOf[PartDao]
          val parts = partDao.findByName("Unit3")
          parts.head.name must beEqualTo("Unit3")
          parts.head.price must beEqualTo(0L)
          parts.head.projectId must beEqualTo(2L)
        }
      }
    }
  }

}

Daoの実装をモックにするのであれば、DaoImplにモックを追加

package daos.impls

import daos._
import models._
import org.squeryl._
import org.squeryl.PrimitiveTypeMode._

class PartDaoImpl extends PartDao{
  def findByName(name: String):Query[Part] = {
    RuntimeDiSampleDb.parts.where(m => m.name like name + "%" )
  }
}

class MockPartDaoImpl extends PartDao {
  def findByName(name: String):Query[Part] = {
    new MockQuery(new Part("Unit3",0,2))
  }
}


class MockQuery(part:Part) extends Query[Part] {
  def iterator: Iterator[Part] = Iterator(part)
  
  // Members declared in org.squeryl.Query
  def ast: org.squeryl.dsl.ast.ExpressionNode = ???
  def copy(asRoot: Boolean): org.squeryl.Query[Part] = ???
  def distinct: org.squeryl.Query[Part] = ???
  def dumpAst: String = ???
  def forUpdate: org.squeryl.Query[Part] = ???
  def invokeYield(rsm: 
   org.squeryl.internals.ResultSetMapper,resultSet: java.sql.ResultSet): models.Part = ???
  def page(offset: Int,pageLength: Int): org.squeryl.Query[Part] = ???
  def statement: String = ???
  
  // Members declared in org.squeryl.Queryable
  def give(resultSetMapper: 
   org.squeryl.internals.ResultSetMapper,rs: java.sql.ResultSet): models.Part = ???
  def name: String = ???
}

最後に、テストクラスでインターフェースにモッククラスを注入。

@RunWith(classOf[JUnitRunner])
class PartDaoSpec extends PlaySpecification{
  "PartDao" should {
    "dao is" in {
      running(FakeApplication()) {
        transaction {
          val injector = new GuiceApplicationBuilder().load(new BuiltinModule, 
            bind[PartDao].to[MockPartDaoImpl]).injector
          val partDao = injector.instanceOf[PartDao]
          val parts = partDao.findByName("Unit3")
          parts.head.name must beEqualTo("Unit3")
          parts.head.price must beEqualTo(0L)
          parts.head.projectId must beEqualTo(2L)
        }
      }
    }
  }

}

いずれのテストでもグリーンバーを表示。