GuiceApplicationBuilder -> evolution пытается получить доступ к prod db при запуске теста
Мы используем
- Scala 2.12.5
- играть 2.6.14 с эволюцией
- пятно 3.2.3
- в многомодульном проекте в IntelliJ 2018.1
В подпроекте "сервис" мы (хотим) использовать Guice для переключения между MySql для prod и H2 для модульного тестирования. Это работало в прошлом, но теперь мы используем также эволюции, которые создают проблемы.
Наш файл application.conf содержит несколько строк для эволюции:
db.default = {
driver="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/dev"
url=${?rds.url}
username="dev-user"
username= ${?rds.user}
password="<secret>"
password=${?rds.password}
}
Один пример теста класса выглядит
class HomeControllerTest extends PlaySpec with GuiceOneAppPerTest with BeforeAndAfterAll {
val application: play.api.Application = new GuiceApplicationBuilder()
.in(Mode.Test)
.overrides(
bind[DbComponent].to(classOf[H2DbComponent])
).build()
// the same for:
override def fakeApplication(): Application = new GuiceApplicationBuilder()...
// the same for:
implicit override lazy val app: play.api.Application = new GuiceApplicationBuilder()...
Проблема: при запуске тестовых классов, которые используют Guice, Evolution пытается получить доступ к базе данных MySql по умолчанию. Это не предназначено и провалит тесты при работе на Jenkins. Когда строки 'db.default = {...}' закомментированы, тест выполняется нормально.
Чтобы преодолеть эту проблему, у нас есть альтернативный файл conf/application.test.conf, содержащий строки для переопределения настроек базы данных "по умолчанию":
db.default = {
driver = "org.h2.Driver"
url = "jdbc:h2:mem:h2test;MODE=MySql;DB_CLOSE_DELAY=-1;INIT=CREATE SCHEMA IF NOT EXISTS dev_db"
}
и добавил в service/build.sbt строку
javaOptions in Test += "-Dconfig.resource=application.test.conf"
оказывается, что альтернативный конфигурационный файл application.test.conf не используется Guice. Это можно проверить, проанализировав значение 'configuration' в play.api.db.DBApiProvider:: get.
Чтобы преодолеть эту проблему, тестовый класс теперь выглядит так
class HomeControllerTest extends PlaySpec with GuiceOneAppPerTest with BeforeAndAfterAll {
val configuration: Configuration = Configuration.reference ++ Configuration(ConfigFactory.parseResources("application.test.conf").resolve)
val application: play.api.Application = new GuiceApplicationBuilder()
.in(Mode.Test)
.loadConfig(configuration)
.overrides(
bind[DbComponent].to(classOf[H2DbComponent])
).build()
Это приводит к ошибке:
Exception encountered when invoking run on a nested suite - Guice configuration errors:
1) No implementation for play.api.Application was bound.
while locating play.api.Application
1 error
com.google.inject.ConfigurationException: Guice configuration errors:
1) No implementation for play.api.Application was bound.
while locating play.api.Application
1 error
at com.google.inject.internal.InjectorImpl.getProvider(InjectorImpl.java:1045)
at com.google.inject.internal.InjectorImpl.getProvider(InjectorImpl.java:1004)
at com.google.inject.internal.InjectorImpl.getInstance(InjectorImpl.java:1054)
at play.api.inject.guice.GuiceInjector.instanceOf(GuiceInjectorBuilder.scala:409)
at play.api.inject.guice.GuiceInjector.instanceOf(GuiceInjectorBuilder.scala:404)
at play.api.inject.ContextClassLoaderInjector.$anonfun$instanceOf$2(Injector.scala:117)
at play.api.inject.ContextClassLoaderInjector.withContext(Injector.scala:126)
at play.api.inject.ContextClassLoaderInjector.instanceOf(Injector.scala:117)
at play.api.inject.guice.GuiceApplicationBuilder.build(GuiceApplicationBuilder.scala:137)
at controllers.HomeControllerTest.fakeApplication(HomeControllerTest.scala:36)
at org.scalatestplus.play.BaseOneAppPerSuite.app(BaseOneAppPerSuite.scala:29)
at org.scalatestplus.play.BaseOneAppPerSuite.app$(BaseOneAppPerSuite.scala:29)
at controllers.HomeControllerTest.app$lzycompute(HomeControllerTest.scala:22)
at controllers.HomeControllerTest.app(HomeControllerTest.scala:22)
at org.scalatestplus.play.BaseOneAppPerSuite.run(BaseOneAppPerSuite.scala:42)
at org.scalatestplus.play.BaseOneAppPerSuite.run$(BaseOneAppPerSuite.scala:41)
at controllers.HomeControllerTest.org$scalatest$BeforeAndAfterAll$$super$run(HomeControllerTest.scala:22)
at org.scalatest.BeforeAndAfterAll.liftedTree1$1(BeforeAndAfterAll.scala:213)
at org.scalatest.BeforeAndAfterAll.run(BeforeAndAfterAll.scala:210)
at org.scalatest.BeforeAndAfterAll.run$(BeforeAndAfterAll.scala:208)
at controllers.HomeControllerTest.run(HomeControllerTest.scala:22)
at org.scalatest.tools.SuiteRunner.run(SuiteRunner.scala:45)
at org.scalatest.tools.Runner$.$anonfun$doRunRunRunDaDoRunRun$13(Runner.scala:1346)
at org.scalatest.tools.Runner$.$anonfun$doRunRunRunDaDoRunRun$13$adapted(Runner.scala:1340)
at scala.collection.immutable.List.foreach(List.scala:389)
at org.scalatest.tools.Runner$.doRunRunRunDaDoRunRun(Runner.scala:1340)
at org.scalatest.tools.Runner$.$anonfun$runOptionallyWithPassFailReporter$24(Runner.scala:1031)
at org.scalatest.tools.Runner$.$anonfun$runOptionallyWithPassFailReporter$24$adapted(Runner.scala:1010)
at org.scalatest.tools.Runner$.withClassLoaderAndDispatchReporter(Runner.scala:1506)
at org.scalatest.tools.Runner$.runOptionallyWithPassFailReporter(Runner.scala:1010)
at org.scalatest.tools.Runner$.run(Runner.scala:850)
at org.scalatest.tools.Runner.run(Runner.scala)
at org.jetbrains.plugins.scala.testingSupport.scalaTest.ScalaTestRunner.runScalaTest2(ScalaTestRunner.java:131)
at org.jetbrains.plugins.scala.testingSupport.scalaTest.ScalaTestRunner.main(ScalaTestRunner.java:28)
В качестве обходного пути к проблеме я изменил все значения по умолчанию для данных подключения для базы данных по умолчанию в application.conf на значения, ожидаемые H2:
// DB connect information - needed for Play Evolutions
db.default = {
driver="org.h2.Driver" // value needed for testing on Jenkins (with H2)
driver=${?rds.driver} // for dev / stage / prod ->"com.mysql.jdbc.Driver"
url="jdbc:h2:mem:h2test;MODE=MySql;DB_CLOSE_DELAY=-1;INIT=CREATE SCHEMA IF NOT EXISTS dev_db" // for testing on Jenkins (H2)
url=${?rds.url} // for dev / stage / prod -> "jdbc:mysql://localhost:3306/<schema-name>"
username="" // value needed for testing on Jenkins (with H2)
username=${?rds.user}
password="" // value needed for testing on Jenkins (with H2)
password=${?rds.password}
}
play.evolutions.enabled=false // value needed for testing on Jenkins (with H2) - evolutions must be disabled
play.evolutions.enabled=${?VALUE} // set to "true" for 'dev' and 'stage'
Но каково чистое, предлагаемое и работающее решение для эволюции тестовых баз данных в памяти?
Спасибо заранее