4

What I want is to run my tests with the same databse engine, same evolutions and config as in production. My db is PostgreSQL 9.4 and I use Slick 3.0.0 to access it.

And here the problems:

  • In case of parallel tests execution I have several evolutions run at once on the same db. That leads to errors.
  • In case of sequential tests execution I have another error with thread pool.

Here the details.

I use evolutions to init the db for each test. For that purpose I prepared a base spec class:

 class DatabaseSpecification extends PlaySpecification {
  protected val defaultAppBuilder =
    new GuiceApplicationBuilder()
      .configure(ConfigurationLoader.loadFirst("application.test.override.conf", "application.test.conf"))

  protected def afterEach(app: Application) = {
    recreateDbSchema(app)
  }

  private def recreateDbSchema(app: Application) = {
    val dbConfig = DatabaseConfigProvider.get[JdbcProfile](app)
    import dbConfig.driver.api._

    val recreateSchema: DBIO[Unit] = DBIO.seq(
      sqlu"drop schema public cascade",
      sqlu"create schema public"
    )    
    Await.ready(dbConfig.db.run(recreateSchema), 5 seconds)
  }

  abstract class DatabaseContext() extends WithApplication(defaultAppBuilder.build()) {
    protected val injector = implicitApp.injector

    override def around[T](t: => T)(implicit evidence$2: AsResult[T]): Result = super.around {
      try {
        t
      } finally {
        afterEach(implicitApp)
      }
    }
  }

}

where application.test.override.conf is config file for the tests.

Also there are couple tests in an ancestor spec:

"save1 and query" in new DatabaseContext {
  // create new user
  val accountRepo = injector.instanceOf[SystemUserRepo]
  val user = new SystemUser(id = 0, login = "admin", passwordHash = "", role = Role.Administrator)
  val futureUserId = accountRepo.create(user)

  // check if user id is greater then zero
  val userId = await(futureUserId)(5 second)
  userId must be_>(0)
}

"second one1" in new DatabaseContext {
  1 mustEqual 1
}

If I run the whole spec's tests in parallel (default) then get exception Database 'default' is in an inconsistent state![An evolution has not been applied properly. Please check the problem and resolve it manually before marking it as resolved.] @6mk338l87: Database 'default' is in an inconsistent state! in one of the tests.

If I run it sequential class AccountRepositoryTest extends DatabaseSpecification { sequential ... } another exeception appears

Task slick.backend.DatabaseComponent$DatabaseDef$$anon$2@2744dcae rejected from java.util.concurrent.ThreadPoolExecutor@16d0e521[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 3]
java.util.concurrent.RejectedExecutionException: Task slick.backend.DatabaseComponent$DatabaseDef$$anon$2@2744dcae rejected from java.util.concurrent.ThreadPoolExecutor@16d0e521[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 3]
    at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
    at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
    at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
    at scala.concurrent.impl.ExecutionContextImpl$$anon$1.execute(ExecutionContextImpl.scala:136)
    at slick.backend.DatabaseComponent$DatabaseDef$class.runSynchronousDatabaseAction(DatabaseComponent.scala:224)
    at slick.jdbc.JdbcBackend$DatabaseDef.runSynchronousDatabaseAction(JdbcBackend.scala:38)
    at slick.backend.DatabaseComponent$DatabaseDef$class.runInContext(DatabaseComponent.scala:201)
    at slick.jdbc.JdbcBackend$DatabaseDef.runInContext(JdbcBackend.scala:38)
    at slick.backend.DatabaseComponent$DatabaseDef$class.runInternal(DatabaseComponent.scala:75)
    at slick.jdbc.JdbcBackend$DatabaseDef.runInternal(JdbcBackend.scala:38)
    at slick.backend.DatabaseComponent$DatabaseDef$class.run(DatabaseComponent.scala:72)
    at slick.jdbc.JdbcBackend$DatabaseDef.run(JdbcBackend.scala:38)
    at repository.GenericCRUD$class.create(GenericCRUD.scala:50)
    at repository.GenericCRUDImpl.create(GenericCRUD.scala:70)
    at unit.repositories.AccountRepositoryTest$$anonfun$3$$anon$1.delayedEndpoint$unit$repositories$AccountRepositoryTest$$anonfun$3$$anon$1$1(AccountRepositoryTest.scala:39)
    at unit.repositories.AccountRepositoryTest$$anonfun$3$$anon$1$delayedInit$body.apply(AccountRepositoryTest.scala:35)

It happens on line val futureUserId = accountRepo.create(user). Sadly but I have no idea why the second exception throws. Btw, I prefer to run tests in parallel of course but don't know how to achive it.

Any help would be appreciated!

sedovav
  • 1,986
  • 1
  • 17
  • 28
  • To simulate any JDBC env during tests, you can have a look at the [Acolyte approach](http://acolyte.eu.org/), which allows isolated unit tests for the persistence layer. – cchantep Jul 03 '15 at 07:54
  • I'm afraid it doesn't help me because I have sql-functions in my db. – sedovav Jul 03 '15 at 22:41
  • As soon as the unit testing of the persistence layer doesn't aim to test the DB engine and/or functions but the application code that works with this DB, the requirement is to be able to define within these tests some JDBC connections accepting the same statement&returning the same JDBC results(resultset|update count|error) as when it plays with the DB.Acolyte does it,whatever is the statement. – cchantep Jul 04 '15 at 00:31

1 Answers1

2

Currently you should running Database tests with sequential else it will fail. The Thread Pool error comes from:

protected val defaultAppBuilder =
    new GuiceApplicationBuilder()
      .configure(ConfigurationLoader.loadFirst("application.test.override.conf", "application.test.conf"))

it should be a function:

protected def defaultAppBuilder =
    new GuiceApplicationBuilder()
      .configure(ConfigurationLoader.loadFirst("application.test.override.conf", "application.test.conf"))

then it will start working. We do this aswell, however we have abstract class which has a val application where we could configure stuff and a def app which will just do a def app = application.build() so that we still could extend the GuiceApplictionBuilder.

Currently I had the same error first, you definitly don't want to have the same application reused for each tests.

So try that and you have a really good functional test suite for play applications.

However the only thing we don't do is the public drop schema we rely on evolutions to fix that by cleaning them up. If they are wrong we instantly see the errors.

Christian Schmitt
  • 837
  • 16
  • 47
  • 1
    Hi Christian! I've changed val to def but unfourtantly have the same error with the thread pool as before. Tell me please how you run evolution downs after a test? – sedovav Jul 04 '15 at 14:09
  • My Evolutions running this way: ```override def after = { val dbapi = app.injector.instanceOf[DBApi] Evolutions.cleanupEvolutions(dbapi.database(prefix)) } ``` and then have a ```with AfterEach``` on your test classes. However that works with your DatabaseContext, too. – Christian Schmitt Jul 06 '15 at 11:03
  • Christian if you don't mind show me your base spec class please. – sedovav Jul 07 '15 at 21:22