Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exception when unit testing an API call using Slick #345

Open
RichyHBM opened this issue Jan 14, 2016 · 37 comments
Open

Exception when unit testing an API call using Slick #345

RichyHBM opened this issue Jan 14, 2016 · 37 comments

Comments

@RichyHBM
Copy link

As noted in playframework/playframework#5466 there is a current issue when unit testing APIs that use Slick

For a minimal repro case, use "activator test" in this project
test.zip

@dotta
Copy link
Contributor

dotta commented Jan 15, 2016

@RichyHBM Hi, I can't immediately see what is the reason for the exception, also considering we have similar tests in the sample projects, e.g., in https://github.com/playframework/play-slick/blob/master/samples/computer-database/test/ApplicationSpec.scala.

Unfortunately, I won't have time to look into it in the foreseeable future, so if you'd like to help out and do some detective work, please go for it :-)

@RichyHBM
Copy link
Author

@dotta
Ok, I believe this may be an issue with the evolutions not having been run in the unit tests.

I have tried a number of things:

  • Adding this to my application.conf, as well as specifying it as part of my FakeApplication additionalConfig
play.evolutions.autoApply=true
play.evolutions.autoApplyDowns=true
play.evolutions.db.default.autoApply=true
play.evolutions.db.default.autoApplyDowns=true
  • This answer, thought I cant construct a fake application from a GuiceApplicationBuilder so it fails to find any database because I imagine this is registered to the original FakeApplication http://stackoverflow.com/a/33399317/1757402
  • And also, via the app.injector (this would get moved in to a before/after)
"add new foo via api" in new WithApplication(fakeApp){
      val dbapi = app.injector.instanceOf[DBApi]
      Evolutions.applyEvolutions(dbapi.database("default"))

      val f = new ApiFoo(0, "test")
      val add = route(FakeRequest(POST, "/async-add", jsonHeaders, f.toJson())).get
      status(add) must equalTo(OK)

      Evolutions.cleanupEvolutions(dbapi.database("default"))
}

The reason I believe its an evolutions issue is when running just the API test without any other tests, I get a Table does not exist error

Also after changing from an in memory database to a file one my database trace file looks like:

org.h2.jdbc.JdbcSQLException: Table "play_evolutions" not found; SQL statement:
select id, hash, apply_script, revert_script, state, last_problem from play_evolutions where state like 'applying_%' [42102-190]

org.h2.jdbc.JdbcSQLException: Table "Foo" not found; SQL statement:
insert into "Foo" ("Name")  values (?) [42102-190]

org.h2.jdbc.JdbcSQLException: Table "Foo" not found; SQL statement:
insert into "Foo" ("Name")  values (?) [42102-190]

I also don't know if this is strictly a play-slick issue, but play framework doesn't seem to think its them, and neither do slick.

@RichyHBM
Copy link
Author

@dotta I believe I have confirmed this,

Running this as the test

val fooTable =
    """ CREATE TABLE Foo (
            Id BIGINT NOT NULL AUTO_INCREMENT,
            Name VARCHAR(50) NOT NULL,
            PRIMARY KEY (Id),
            UNIQUE ( Name )
    );"""

  "Application" should {

    "add new foo via api" in new WithApplication(fakeApp){
      val con = app.injector.instanceOf[DBApi].database("default").getConnection()
      val c = con.prepareCall( fooTable )
      c.execute()
      c.close()

      val f = new ApiFoo(0, "test")
      val add = route(FakeRequest(POST, "/async-add", jsonHeaders, f.toJson())).get
      status(add) must equalTo(OK)
    }
  }

Makes it pass, I can add as many API tests into it and they all pass.

However if I add in a normal database test:

"Add foo" in new WithApplication(fakeApp){
    Foos.add(new Foo(0, "TEST")).map(r => r must equalTo(1))
}

Any API tests that are run after it will fail!

@dotta
Copy link
Contributor

dotta commented Jan 15, 2016

Nice findings! The evolutions integration in play-slick is very minimalist as you can see here. All it does is providing an implementation of the Play Database abstraction that uses Slick underneath.

From your last message is almost seems to me that the Database is being closed after running a test that triggers the Slick evolutions support. Not sure why that is the case, but I think you are pretty close to find the reason for the issue. Thanks a ton for looking into this!

@RichyHBM
Copy link
Author

Well it looks to be some issue with either the tear down or the setup when using a combination of things, Specs, H2, Slick..

I had a look but can't seem to find the issue, so instead I wrote a (very ugly) Scala class that will just run all the defined tests in a single wrapped Spec which avoids this issue.

I.E. I can just do:

class ApplicationSpec extends ApiTestHelper[ApplicationSpec] {
  def getThis = this

  "SubClassSpec" should {
    "run after API tests" in {
      1 must equalTo(1)
    }
  }

  def AddFoo = {
    val add = route(FakeRequest(POST, "/async-add", jsonHeaders, ApiFoo(0, "test1").toJson())).get
    status(add) must equalTo(OK)
  }

  def AddFoo2 = {
    val add = route(FakeRequest(POST, "/async-add", jsonHeaders, ApiFoo(0, "test2").toJson())).get
    status(add) must equalTo(OK)
  }
}

Which produces:

[info] ApplicationSpec
[info]
[helper] ApplicationSpec should:
[helper]   + AddFoo
[helper]   + AddFoo2
[helper]
[info] ApplicationSpec should
[info]   + run all API tests
[info] SubClassSpec should
[info]   + run after API tests
[info]
[info] Total for specification ApplicationSpec
[info] Finished in 747 ms

I don't know if this bug is actually a play-slick issue so feel free to close it if you think best

@dotta
Copy link
Contributor

dotta commented Jan 16, 2016

I think it's worth keeping it open until we have full resolution. Thanks for providing a workaround, I'm sure people will find it useful.

@RichyHBM
Copy link
Author

Just thought I would leave a little more information on the workaround that I have noticed this weekend, my original method using the class that runs tests within a single test didn't really work as expected once I moved it from my simple example to my larger project, it would fix some tests but others would still fail.

Instead I have found a solution that seems to fix this, or at least fully gets around it.
In my build.sbt

fork in test := true

def singleJvmPerGroup(tests: Seq[TestDefinition]) = {
  tests map { test =>
    import Tests._
    test match {
      case t if t.name.startsWith("controllers") => new Group(t.name, Seq(t), SubProcess(javaOptions = Seq.empty[String]))
      case t => new Group(t.name, Seq(t), InProcess)
    }
  }
}

testGrouping in Test <<= definedTests in Test map singleJvmPerGroup

I know the only tests that are going to run into this issue are ones within the controllers package, so I fork these tests and run them inside a new JVM which makes sure what ever issue that is causing this is no longer there, I keep all other tests running in the current JVM as creating a new one adds about half a second to each test suite.

If you wanted to you could mark tests with this specific issue and not for for all tests under controller but this works for now.

However, this does make SBT report erroneous quantities of tests:
[info] Passed: Total 0, Failed 0, Errors 0, Passed 0

I think ideally you would just order the tests so that all the problematic ones are run first and then all other ones are run, but there doesn't seem to be a way to specify SBT test ordering its just the order in which the JVM returns the test Specifications

@RichyHBM
Copy link
Author

Final thing just for anyone that may come across this having the same issue, this is how I finally fixed it.
Worth saying I wouldn't go with the above as I found some bugs saying SBT can incorrectly report bad tests as passed, plus its nice to have all test numbers reported correctly!

I have added this to my SBT, it sorts test names by their lexicographical value and then puts them all into the same Group. Having them in the same group is important as its what makes SBT report the correct number of tests!

//Put all tests into a single Group so that SBT reports correct number of tests, but order them by test name
testGrouping in Test <<= definedTests in Test map { tests => {
    val sortedTests = tests map {
      test => new Tests.Group(test.name, Seq(test), Tests.InProcess)
    } sortBy (_.name.toLowerCase) flatMap {
      _.tests
    }
    Seq(new Tests.Group("Tests", sortedTests, Tests.InProcess))
  }
}

I then created a package within my test folder named batched, this is important as you want anything within this folder to be run first. All my other tests are in packages which name start after b this anything in my batched folder will be run first

Finally, all requests need to be made within the same Specification example, so for example:

"Application" should {

    "run all api tests" in new WithApplication(fakeApp){
      val f = new ApiFoo(0, "test")
      val add = route(FakeRequest(POST, "/async-add", jsonHeaders, f.toJson())).get
      status(add) must equalTo(OK)

      val f2 = new ApiFoo(0, "test2")
      val add2 = route(FakeRequest(POST, "/async-add", jsonHeaders, f2.toJson())).get
      status(add2) must equalTo(OK)

      ...
    }
  }

Annoyingly this means that you don't get a test per API call, but at least they all get tested (as long as none of them throw an error)
What I have done is write an abstract class that will run all methods of its subclass within a single Specs Example, it also prints the tests as SBT would if they were run individually

The main work around for this whole issue is having your API test run before any others, and within the same FakeApplication scope. The method I have outlined feels a bit hacky but I have spent all weekend trying to figure out a way of doing this in a more clean fashion and I just cant think of any, so if anyone figures out a nicer way to do it please do say!

@buildreactive
Copy link

I'm seeing this problem as well... a very simple test application, single table. Using specs2 for a basic test: first insert a row, then query for a single row by id.

class TestCartPersistence extends PlaySpecification with ScalaCheck with AutomationTestUtilities with Generators with TestLogging {
    "A CartRepository" should {
        "store a cart in the database" in new WithApplication {
            val c: CartModel = CartModel(None, None, None, None, CartStatus.Prepurchase, CartType.Journey, 1L, None)
            val f: Future[CartModel] = CartRepository.save(c)
            f must not beNull
        }

        "be able to retrieve a list of people in the database" in new WithApplication  {
            val f: Future[Option[CartModel]] = CartRepository.findById(1L)
            f must not beNull
        }
    }
}

The second test fails with:

[info]   * retrieve a single cart from the database PENDING
[error]   ! be able to retrieve a list of people in the database
[error]    java.util.concurrent.RejectedExecutionException: Task slick.backend.DatabaseComponent$DatabaseDef$$anon$2@100d5dc8 rejected from java.util.concurrent.ThreadPoolExecutor@6e6b0e6a[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 1] (DatabaseComponent.scala:230)
[error] slick.backend.DatabaseComponent$DatabaseDef$class.runSynchronousDatabaseAction(DatabaseComponent.scala:230)
[error] slick.jdbc.JdbcBackend$DatabaseDef.runSynchronousDatabaseAction(JdbcBackend.scala:38)
[error] slick.backend.DatabaseComponent$DatabaseDef$class.runInContext(DatabaseComponent.scala:207)
[error] slick.jdbc.JdbcBackend$DatabaseDef.runInContext(JdbcBackend.scala:38)
[error] slick.backend.DatabaseComponent$DatabaseDef$class.runInternal(DatabaseComponent.scala:75)
[error] slick.jdbc.JdbcBackend$DatabaseDef.runInternal(JdbcBackend.scala:38)
[error] slick.backend.DatabaseComponent$DatabaseDef$class.run(DatabaseComponent.scala:72)
[error] slick.jdbc.JdbcBackend$DatabaseDef.run(JdbcBackend.scala:38)
[error] repositories.CartRepository$.findById(CartRepository.scala:23)
[error] models.TestCartPersistence$$anonfun$2$$anonfun$apply$4$$anon$2.delayedEndpoint$models$TestCartPersistence$$anonfun$2$$anonfun$apply$4$$anon$2$1(TestCartPersistence.scala:39)
[error] models.TestCartPersistence$$anonfun$2$$anonfun$apply$4$$anon$2$delayedInit$body.apply(TestCartPersistence.scala:38)
[error] play.api.test.WithApplication$$anonfun$around$2.apply(Specs.scala:39)
[error] play.api.test.WithApplication$$anonfun$around$2.apply(Specs.scala:39)
[error] play.api.test.PlayRunners$class.running(Helpers.scala:42)
[error] play.api.test.Helpers$.running(Helpers.scala:363)
[error] play.api.test.WithApplication.around(Specs.scala:39)
[error] play.api.test.WithApplication.delayedInit(Specs.scala:36)
[error] models.TestCartPersistence$$anonfun$2$$anonfun$apply$4$$anon$2.<init>(TestCartPersistence.scala:38)
[error] models.TestCartPersistence$$anonfun$2$$anonfun$apply$4.apply(TestCartPersistence.scala:38)
[error] models.TestCartPersistence$$anonfun$2$$anonfun$apply$4.apply(TestCartPersistence.scala:38)

@buildreactive
Copy link

A little more information on my previous post:

Adding to @RichyHBM's findings – indeed, I can execute several Slick calls within the same test case so long as its in a single WithApplication:

class TestCartPersistence extends PlaySpecification with ScalaCheck with AutomationTestUtilities with Generators with TestLogging {
    "A CartRepository" should {
        // This will run just fine...
        "store a cart in the database a couple times" in new WithApplication {
            val c: CartModel = CartModel(None, None, None, None, CartStatus.Prepurchase, CartType.Journey, 1L, None)
            val f: Future[CartModel] = CartRepository.save(c)
            f must not beNull

            val c2: CartModel = CartModel(None, None, None, None, CartStatus.Prepurchase, CartType.Journey, 1L, None)
            val f2: Future[CartModel] = CartRepository.save(c2)
            f2 must not beNull
        }

        // But this will blow up :-(
        "be able to retrieve a list of people in the database" in new WithApplication  {
            val f: Future[Option[CartModel]] = CartRepository.findById(1L)
            f must not beNull
        }
    }
}

@RichyHBM
Copy link
Author

@zbeckman Interesting that you have experienced this when interacting directly with your DAO (CartRepository) I only experience this when the DAO is called from a controller (that is called from the test)

This does not cause me any exceptions:

"UserDao" should {
    "Add user" in new WithApplication(fakeApp){
      Users.add(user).map(r => r must equalTo(1))
    }

    "Not add duplicates" in new WithApplication(fakeApp){
      Users.add(user).map(r => r must equalTo(1))
      Users.add(user).map(r => r must equalTo(0))
    }

    "Delete added user" in new WithApplication(fakeApp){
      Users.add(user).map(r => r must equalTo(1))
      Users.delete(1).map(r => r must equalTo(1))
    }

    "Get by id" in new WithApplication(fakeApp){
      Users.add(user).map(r => r must equalTo(1))
      Users.get(1).map(r => r must equalTo(Some))
    }
}

Where fakeApp is:

def fakeApp: FakeApplication = FakeApplication(additionalConfiguration = Map(
    "slick.dbs.default.driver" -> "slick.driver.H2Driver$",
    "slick.dbs.default.db.driver" -> "org.h2.Driver",
    "slick.dbs.default.db.url" -> "jdbc:h2:mem:test;MODE=PostgreSQL;DATABASE_TO_UPPER=FALSE"
  ))

@buildreactive
Copy link

Agreed. Last night I removed play-slick from my configuration, and am now using straight up Slick (3.1.1), and it's working fine. So there must be something in the play-slick integration that's causing this problem.

FYI, I also disabled evolutions, but that did not seem to affect the problem in any way...

@MarkRBM
Copy link

MarkRBM commented Jan 20, 2016

Same issue here @RichyHBM workarounds dosn't seem to help but I might be missing something.

I am not using the evolutions but I am using Databases.withInMemory and then extends WIthApplication and overriding the arround method to run my schema creation

Await.result(db.run(sql"""CREATE SCHEMA IF NOT EXISTS "dbo"""".as[String]), Duration.Inf) seems to cause the problem where as if I comment that line out the next line

Await.result(db.run(Visitors.schema.create), Duration.Inf)

seems to run ok but returns an error because it cant find the schema dbo

Task slick.backend.DatabaseComponent$DatabaseDef$$anon$2@4034e08d rejected from java.util.concurrent.ThreadPoolExecutor@1f4b651[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 2] (DatabaseComponent.scala:230)

Is my specific error

@dotta
Copy link
Contributor

dotta commented Jan 21, 2016

Hi Everyone, sorry to hear so many of you are hitting the same problem. Unfortunately, I don't have any spare cycle at the moment to look at it, but I'd encourage you to try to debug the issue. After all, play-slick is a really small module, so there isn't a lot of code that needs to be understood. Just set a breakpoint in your test and explore what's happening on the play-slick code, hopefully the issue will reveal itself quickly.

@RichyHBM
Copy link
Author

@zbeckman @ir1sh I just noticed something, if I wrap the offending code inside a DAO interaction it works!

For example, if this would fail

"Do test" in new WithApplication {
    val response = route(FakeRequest(...)).get
    status(response) must equalTo(OK)
}

By wrapping it in the map of a DAO it works!

"Do test" in new WithApplication {
    SomeDAO.add(...).map(r => {
        val response = route(FakeRequest(...)).get
        status(response) must equalTo(OK)
    })
}

@dotta Not expecting you to jump on this right away! :)

This does seem more and more like the evolutions aren't being applied correctly in some circumstances

@RichyHBM
Copy link
Author

@ir1sh You are completely right!

I had not realised this because I had been writing all my tests in the style of:

Users.add(user).map(r => r must equalTo(1))

But this has actually not been running them properly! Inserting a print inside the map would not print anything out..

I have begun changing my tests to use Await and now they are all failing for this reason.

What version of Play are you using? When I try to use Databases.withInMemory it complains Databases can not be found

@MarkRBM
Copy link

MarkRBM commented Jan 22, 2016

play 2.4.x I Actually dont know where to look to find the x value

Here is my complete database helper for my tests. Commenting out the first line in the for comprehension in createTables function gives the behaviour I described earlier

I am not using evolutions. I will try your dao trick later and see if that helps.

object TestDatabase {
  class testDbProvider extends DatabaseConfigProvider {
    def get[P <: BasicProfile]: DatabaseConfig[P] = {
      DatabaseConfigProvider.get("default")(Play.current)
    }
  }

  def startTestDb[T](block: Database => T) = {
    Databases.withInMemory(
      name = "testDB",
      urlOptions = Map(
        "MODE" -> "MSSQLServer"
      ),
      config = Map(
        "logStatements" -> true
      )
    )(block)
  }

  abstract class WithTestDb extends WithApplication((TestApplication.application)) {
    lazy val profile = slick.driver.H2Driver
    import profile.api._

    lazy val db = new testDbProvider().get.db

    override def around[T: AsResult](t: => T): Result = super.around {
      setupData()
      t
    }

    val allTables = (VisitorSignatures.schema ++ VisitorImages.schema ++ Visitors.schema ++ VisitorStatuses.schema ++ VisitorRegistrations.schema
    ).create

    def createSchema = {
//      Await.result(db.run(sql"""CREATE SCHEMA IF NOT EXISTS "dbo"""".as[String]), Duration.Inf)
      Logger.info("TRYING TO CREATE THE SCHEMA")
    }

    /** Create all tables in database */
    def createTables = {
      import scala.concurrent.ExecutionContext.Implicits.global
      Await.result(for {
        y <- db.run(sql"""CREATE SCHEMA IF NOT EXISTS "dbo"""".as[String])
        x <- db.run(Visitors.schema.create)
      } yield x, Duration.Inf)
      Logger.info("TRYING TO CREATE THE tables")
    }

    /** Delete all tables in database */
    def drop = {
//      allTables.drop
    }

    def setupData() {
      // setup data
//      createSchema
      createTables
    }
  }

}

@RichyHBM
Copy link
Author

@ir1sh If you mean wrapping the tests inside a DAO result map then don't do this, I didn't realise the map returns instantly so the test never actually runs

What I am now doing, and seems to work is creating a global Application for the unit tests:

object Statics {
  val conf = Map(
    "slick.dbs.default.driver" -> "slick.driver.H2Driver$",
    "slick.dbs.default.db.driver" -> "org.h2.Driver",
    "slick.dbs.default.db.url" -> "jdbc:h2:mem:test;MODE=PostgreSQL;DATABASE_TO_UPPER=FALSE"
  )

  val fakeApp: FakeApplication = new FakeApplication(additionalConfiguration = conf)

  Play.start(fakeApp) /// <--- This creates the application
}

And removing the WithApplication from all my specifications, that way they all use the unique application

@MarkRBM
Copy link

MarkRBM commented Jan 22, 2016

@RichyHBM ok cool I will try that, do you know if you can give each test class a fresh db with this global application method? I don't want my tests to rely on state left in the db from previously run tests.

@RichyHBM
Copy link
Author

@ir1sh Something like this should hopefully work

class FreshDatabaseSpecification extends Specification with BeforeAfterEach {
  private def dbApi = Play.current.injector.instanceOf[DBApi]
  override protected def before: Any = Evolutions.applyEvolutions(dbApi.database("default"))
  override protected def after: Any = Evolutions.cleanupEvolutions(dbApi.database("default"))
}

object FreshDatabaseSpecification {
  Play.start(fakeApp)
}

Have all your Specifications extend FreshDatabaseSpecification

@RichyHBM
Copy link
Author

@ir1sh Actually, this is probably better as I have had the above occasionally throw exceptions because the Application hasn't been started before the before is called.

object Helpers {
  def WithFreshDatabase[T](block: => T) = {
    val dbApi = Play.current.injector.instanceOf[DBApi]
    Evolutions.cleanupEvolutions(dbApi.database("default"))
    Evolutions.applyEvolutions(dbApi.database("default"))
    block
  }

  val conf = Map(
    "slick.dbs.default.driver" -> "slick.driver.H2Driver$",
    "slick.dbs.default.db.driver" -> "org.h2.Driver",
    "slick.dbs.default.db.url" -> "jdbc:h2:mem:test;MODE=PostgreSQL;DATABASE_TO_UPPER=FALSE"
  )
  val fakeApp: FakeApplication = new FakeApplication(additionalConfiguration = conf)

  Play.start(fakeApp)
}

And then just:

"Do test" in Helpers.WithFreshDatabase {
    SomeDAO.add(...).map(r => {
        val response = route(FakeRequest(...)).get
        status(response) must equalTo(OK)
    })
}

@MarkRBM
Copy link

MarkRBM commented Jan 26, 2016

@RichyHBM Only getting back to this now. How are you creating the h2 db in the first place? Databases.withInMemory elsewhere? Also what is DBApi?

@RichyHBM
Copy link
Author

@ir1sh

H2 database is created by using a FakeApplication with some custom config

val conf = Map(
    "slick.dbs.default.driver" -> "slick.driver.H2Driver$",
    "slick.dbs.default.db.driver" -> "org.h2.Driver",
    "slick.dbs.default.db.url" -> "jdbc:h2:mem:test;MODE=PostgreSQL;DATABASE_TO_UPPER=FALSE"
  )
  val fakeApp: FakeApplication = new FakeApplication(additionalConfiguration = conf)

The DBApi is part of play and provides access to the databases
https://www.playframework.com/documentation/2.4.x/api/scala/index.html#play.api.db.DBApi

@MarkRBM
Copy link

MarkRBM commented Jan 26, 2016

Thanks @RichyHBM this approach seems to work, at least I get different errors now 👍

@tg44
Copy link

tg44 commented Feb 18, 2016

I have the same problem here!
Have any actually working solution to this? (I really want a solution and not a workaround where all my testcases go into other files, or I shoud rape my build.sbt to do some magic.)
I tried the above mentioned Helpers.WithFreshDatabase, but its failing with inconsistent state exception after my first test, so I think its only working with sequential test running...

@tg44
Copy link

tg44 commented Feb 25, 2016

@RichyHBM Your code is worked? I get exceptions/fails from paralell like execution after I "totally" turned off paralell execution.

Helper:

def WithFreshDatabase[T](block: => T): T = {
  Thread.sleep(r.nextInt(1000)) //random sleep still not helping
  val dbApi = Play.current.injector.instanceOf[DBApi]
  Evolutions.cleanupEvolutions(dbApi.database("default"))
  Evolutions.applyEvolutions(dbApi.database("default"))
  val ret: T = block
  //ClearDB.runAwait
  ret
}
val r = scala.util.Random

Build.sbt

parallelExecution in Test := false
//parallelExecution in ThisBuild := false
//concurrentRestrictions in Global += Tags.limit(Tags.Test, 1)
//fork in Test := true

I tried all of the outcommented lines, but I get fails because of the tests inserts of other tests inmemory database (like they run paralell).
Any idea?

@RichyHBM
Copy link
Author

@tg44 Hmm, I'm not sure, it works fine on my code :s
Have you tried using the sequential keyword in the unit test?
I.E like the code example in http://stackoverflow.com/questions/13173319/why-does-specs2-run-these-sequential-tests-in-random-order

@tg44
Copy link

tg44 commented Feb 25, 2016

Of course I tried :D no positive result... 1-2 test always mess up others...

@cipacda
Copy link

cipacda commented Feb 26, 2016

I've got the same problem and I am failing to work around it..

@MarkRBM
Copy link

MarkRBM commented Feb 26, 2016

@dotta I see that there are commits being made to master and I understand that everyone is busy but do you think there is any way to put the resolution of this bug on the roadmap along with the upgrade to play2.5 at least? If I had the knowledge to investigate I would.

@cipacda
Copy link

cipacda commented Mar 1, 2016

@dotta This also happens on version 2.0.0-M1 with Play 2.5 as well.

@cipacda
Copy link

cipacda commented Mar 1, 2016

I have finally managed to WORKAROUND this by using Play's full DI, without having pretty much any objects lying around

@Leammas
Copy link

Leammas commented Mar 6, 2016

@cipacda Can you provide some code examples please? Since there's Play 2.5 release, using app as implicit parameter like in the computer-database sample is no longer an option.

@MarkRBM
Copy link

MarkRBM commented Apr 8, 2016

@cipacda any code samples by any chance? My workaround seems to have broken between 2.4 and 2.5

@cipacda
Copy link

cipacda commented Apr 10, 2016

My controllers looks like this:

@Singleton
class MyController @Inject() (myDao: MyDAO) { .. }

and I manually create the fake app like this:

 def fakeApp = {
    new GuiceApplicationBuilder()
      .configure("slick.dbs.default.db.url" -> s"jdbc:h2:mem:play-test-${scala.util.Random.nextInt()};MODE=PostgreSQL;DATABASE_TO_UPPER=false")
      .overrides(bind[MailerClient].to[MockMailerClient])
      .build()
  }

so a new database is used for each test.

Since I've changed to this structure I didn't hit the problem anymore.

Hope that helps!

@mach-kernel
Copy link

@cipacda I tried to replicate your pattern but I am getting this:

[info] Exception encountered when attempting to run a suite with class name: org.scalatest.DeferredAbortedSuite *** ABORTED ***
[info]   java.lang.NullPointerException:
[info]   at org.birdfeed.chirp.database.Query$class.$init$(Query.scala:15)
[info]   at org.birdfeed.chirp.test.database.QuerySpec.<init>(QuerySpec.scala:19)
[info]   at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
[info]   at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
[info]   at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
[info]   at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
[info]   at java.lang.Class.newInstance(Class.java:442)
[info]   at org.scalatest.tools.Framework$ScalaTestTask.execute(Framework.scala:468)
[info]   at sbt.ForkMain$Run$2.call(ForkMain.java:296)
[info]   at sbt.ForkMain$Run$2.call(ForkMain.java:286)

I followed typical DI pattern other than making a similar fakeApp method because the default OneServerPerTest already uses Guice DI so I didn't think it necessary. I guess I am asking if this error rings a bell to anybody here having been through the steps of debugging this previously?

Thanks!

@mach-kernel
Copy link

mach-kernel commented Nov 6, 2016

I fixed it with a pattern similar to @cipacda. My pattern is as follows.

I have a query trait that I mix in to any controllers that require database actions. "Children" of Query use this local dbConfigProvider:

trait Query {
    val dbConfigProvider: DatabaseConfigProvider
    ...
}

When mixing this trait into controllers, I do:

class WhateverController @Inject()(actorSystem: ActorSystem, val dbConfigProvider: DatabaseConfigProvider) with Query

And if I were to test against the Query trait:

class QuerySpec extends PlaySpec with OneServerPerSuite with Query {
  val dbConfigProvider = app.injector.instanceOf(classOf[DatabaseConfigProvider])
  val dbConfig = dbConfigProvider.get[JdbcProfile]
}

I can run all of my tests without any problems now. I hope this can help someone! 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants