7

To have a database available in scalatest with evolutions I use this extension of the default PlaySpec inspired by this SO question:

trait ResetDbSpec extends PlaySpec with BeforeAndAfterAll {
  lazy val appBuilder = new GuiceApplicationBuilder()
  lazy val injector = appBuilder.injector()
  lazy val databaseApi = injector.instanceOf[DBApi]

  override def beforeAll() = {
    Evolutions.applyEvolutions(databaseApi.database("default"))
  }

  override def afterAll() = {
    Evolutions.cleanupEvolutions(databaseApi.database("default"))
    databaseApi.database("default").shutdown()
  }
}

It applies database evolutions when the suite starts, and reverts them when the suite ends. A test then looks like

class ProjectsSpec extends ResetDbSpec with OneAppPerSuite { ...

After adding more tests like this, I hit a point where some tests that succeed when I run them alone, fail with this error:

com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: Data source rejected establishment of connection, message from server: "Too many connections"

As can be see in the code above, I tried to add the line

databaseApi.database("default").shutdown()

in afterAll() to mitigate that, but it had no effect. I tried to not run tests in parallel, but no effect either. Where is it that I open db connections without closing them, and where should I call shutdown()?

N.B. I use Play 2.5.10 and Slick 3.1.

Community
  • 1
  • 1
JulienD
  • 7,102
  • 9
  • 50
  • 84
  • Is the application trying to keep more connections in the pool than the database will allow open? – Michael Zajac Feb 21 '17 at 13:58
  • @MichaelZajac How can I verify that? – JulienD Feb 21 '17 at 14:08
  • Does changing `lazy val databaseApi` to `def databaseApi` help? Otherwise, you can check https://www.playframework.com/documentation/2.5.x/SettingsJDBC to tweak the maximum pool size. Also, like @MichaelZajac said, you could check your local database configuration to see how many connections it allows. – Eric Feb 21 '17 at 15:19
  • @Eric `def databaseApi` did not help. I am reading about how to set the pool size with Slick but it is rather confuse :( It says to look at some JDBC `forConfig` thing "but most of it is ignored", then increase the number of threads or queue size. https://www.playframework.com/documentation/2.5.x/PlaySlickAdvancedTopics#connection-pool. Should I do the latter? – JulienD Feb 21 '17 at 15:46
  • I set `numThreads=200` and `queueSize=500` in slick.dbs.default, but it had no effect. I also tried `maxConnections=5000`. Edit: in "slick.dbs.default.db" the "connectionTimeout" attribute had an effect (to wait for longer), so I moved everything there but it still doesn't work. – JulienD Feb 21 '17 at 15:57
  • I can also see this [debug] line: `[debug] c.z.h.p.PoolUtilities - Closing connection null` – JulienD Feb 21 '17 at 16:04
  • I'm not sure if creating a `GuiceApplicationBuilder` and then using its `Injector` is a good option here. But why are you not using the `Database` [helpers](https://www.playframework.com/documentation/2.5.x/ScalaTestingWithDatabases#Using-a-database)? Specially [`Database.withDatabase`](https://www.playframework.com/documentation/2.5.x/ScalaTestingWithDatabases#Allowing-Play-to-manage-the-database-for-you) so that Play will manage the database for you. Also, maybe you can change `ResetDbSpec` to extends trait `OneAppPerSuite` and then use the managed app that comes from there. – marcospereira Feb 21 '17 at 18:44
  • @marcospereira I suppose because that `withMyDatabase` code has to be repeated in every single test (I tried one for the whole suite and it never worked), which means evolutions also, and that represents thousands of useless lines. `OneAppPerSuite` for some reason is not enough to provide a suitable Application: http://stackoverflow.com/questions/42021022/play-slick-how-to-inject-dbconfigprovider-in-tests (see comments). I would love to see once and copy a single "standard" working example of testing a DAO with Slick. – JulienD Feb 22 '17 at 09:23
  • @marcospereira Also `Databases` requires jdbc which is not compatible with Slick. – JulienD Feb 22 '17 at 09:33
  • @Eric I think you are on the right track because replacing the beforeAll/afterAll code with only `println(databaseApi)` causes the same error, while if I remove it it works. – JulienD Feb 22 '17 at 12:54
  • @JulienD what do you mean by "jdbc is not compatible with Slick"? Slick uses JDBC underlying to access the database. You can also use `Database.withDatabase` and have basically the same result you are trying to achieve (a single evolution for the whole suite): https://gist.github.com/marcospereira/fc47ab111f26b7594668864842866acc – marcospereira Feb 22 '17 at 22:13
  • @marcospereira At some point I had to remove 'jdbc' from `libraryDependencies`, although I can't remember where I read that, and that is where `Databases` comes from. If I add it back, I get `A binding to play.api.db.DBApi was already configured...`. `Database` (without 's') has no member `withDatabase`. – JulienD Feb 23 '17 at 01:02

2 Answers2

1

I have a lot of tests (about 500) and I don't get this error, the only difference I have with your code is that I add

databaseApi.database("default").getConnection().close()

and

Play.stop(fakeApplication)

for the integration tests.

Let me know if it changes anything.

Simon
  • 6,025
  • 7
  • 46
  • 98
  • Unfortunately it does not :(. As soon as I call `databaseApi` in either BeforeAll or AfterAll I get the error (i.e. the lazy val is evaluated). Maybe I misunderstood; I added these two lines you suggest in `afterAll()`. I have only ~120 tests. Maybe MySQL, or the slick driver for it, is for something in this. – JulienD Feb 22 '17 at 13:30
  • No I think that you understood well. If it can help, I use PostgreSQL and I change nothing of the database, slick or hikariCP configuration (at least for the tests) – Simon Feb 22 '17 at 14:21
1

Although it does not answer to what is happening with the connections leakage, I finally managed to hack around this:

  1. Add jdbc to you libraryDependencies, even if the Play-Slick FAQ tells you not to do it:

    # build.sbt
    libraryDependencies += jdbc
    

    Restart sbt to take changes into account. In IntelliJ, you will want to refresh the project, too.

  2. Disable the jdbc module that is conflicting with play-slick (credits: this SO answer):

    # application.conf
    play.modules.disabled += "play.api.db.DBModule"
    

    At the same place you should have already configured something like

    slick {
      dbs {
        default {
          driver = "slick.driver.MySQLDriver$"
          db.driver = "com.mysql.jdbc.Driver"
          db.url = "jdbc:mysql://localhost/test"
          db.user = "sa"
          db.password = ""
        }
      }
    }
    
  3. Now you can use play.api.db.Databases from jdbc and its method withDatabase to run the evolutions.

    import org.scalatest.BeforeAndAfterAll
    import org.scalatestplus.play.PlaySpec
    import play.api.db.{Database, Databases}
    import play.api.db.evolutions.Evolutions
    
    
    trait ResetDbSpec extends PlaySpec with BeforeAndAfterAll {
    
      /**
       * Here we use Databases.withDatabase to run evolutions without leaking connections.
       * It slows down the tests considerably, though.
       */
    
      private def withTestDatabase[T](block: Database => T) = {
        Databases.withDatabase(
          driver = "com.mysql.jdbc.Driver",
          url = "jdbc:mysql://localhost/test",
          name = "default",
          config = Map(
            "username" -> "sa",
            "password" -> ""
          )
        )(block)
      }
    
      override def beforeAll() = {
        withTestDatabase { database =>
          Evolutions.applyEvolutions(database)
        }
      }
    
      override def afterAll() = {
        withTestDatabase { database =>
          Evolutions.cleanupEvolutions(database)
        }
      }
    
    }
    
  4. Finally, call tests requiring a db reset like this:

    class MySpec extends ResetDbSpec {...}
    

Of course it sucks repeating this config both in "application.test.conf" and in withDatabase(), plus it mixes two different APIs, not talking about performance. Also it adds this before and after each suite, which is annoying:

[info] application - Creating Pool for datasource 'default'
[info] application - Shutting down connection pool.

If somebody has a better suggestion, please improve on this answer! I have been struggling for months.

Community
  • 1
  • 1
JulienD
  • 7,102
  • 9
  • 50
  • 84