はじめに
以下のような依存関係のあるアプリがあって、ApplicationにはMyServiceが、MyServiceにはMyRepositoryが注入されているとする。
Application(Controller) -> MyService -> MyRepository
この時、MyServiceのテストをしたいと思った時、Applicationは不要なわけで、となるとさて、どうやってテスト対象のMyServiceを得るんだっけ?となったのが今回のポストのきっかけ。
※なお、実行環境によって本番用モジュールとテスト用モジュールを切り替えてテストする方法については以下にまとめています。
Play2.4のDIについて動作確認(Guiceの使い方)
動作確認
ベースプログラム
まず、今回使うプログラム群を示す。
ControllerであるApplication.scalaは activator new
で生成できるテンプレのやつをベースにして、 service.run()
を実行する。
@Singleton
class Application @Inject() (service: MyService) extends Controller {
def index = Action {
println(service.run())
Ok(views.html.index("Your new application is ready."))
}
}
MyServiceは
@ImplementedBy(classOf[MyServiceForProduction])
trait MyService {
def run(): String
}
@Singleton
class MyServiceForProduction @Inject() (@Named("production-rep") repository: MyRepository) extends MyService {
def run() = "production: " + repository.findAll().size
}
@Singleton
class MyServiceForTest @Inject() (@Named("test-rep") repository: MyRepository) extends MyService {
def run() = "test: " + repository.findAll().size
}
MyRepository#findAllの戻り値型はちょっとサボってます。。
trait MyRepository {
def findAll(): Seq[Int]
}
@Singleton
class MyRepositoryForProduction extends MyRepository {
def findAll() = Seq(1,2,3,4,5)
}
@Singleton
class MyRepositoryForTest extends MyRepository {
def findAll() = Seq(-1)
}
class MyRepositoryModule extends AbstractModule {
override def configure(): Unit = {
bind(classOf[MyRepository])
.annotatedWith(Names.named("production-rep"))
.to(classOf[MyRepositoryForProduction])
bind(classOf[MyRepository])
.annotatedWith(Names.named("test-rep"))
.to(classOf[MyRepositoryForTest])
}
}
最後にModule定義を追加。
play.modules.enabled += "modules.MyRepositoryModule"
http://localhost:9000/
にアクセスすれば、コンソールに production: 5
と表示される。
無事、本番用のモジュール群が注入されていることが確認できた。
MyServiceをテストしたい
Controllerからじゃなくて途中のServiceからテストしたい、とした場合誰がMyServiceForTestの方をnewしてしかも依存性の注入まで行うのか、というのが当初よく分からなかった。
Guiceのマニュアルを読んでInjectorを使って生成する事を知った。以下、テストコード。
class MyServiceSpec extends FlatSpec with Matchers {
"MyService" should "be MyServiceForTest" in {
val injector = Guice.createInjector(
new AbstractModule(){
override def configure(): Unit = bind(classOf[MyService]).to(classOf[MyServiceForTest])
},
new MyRepositoryModule())
val service = injector.getInstance(classOf[MyService])
service.run() should be ("test: 1")
}
}
これで無事、Controllerからじゃなくても途中のモジュールをテストできる事が分かった。