Playframework 2.4 ,Guice,squeryl でテスト
Playframework2.4がリリースされた。
目玉はDIの模様。ドキュメントを確認すると、googleのGuiceを利用とのこと。
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) } } } } }