23

I don't know how to configure GuiceApplicationBuilder in such a way, that I am able to load controllers that require a DatabaseConfigProvider to be injected.

I'd like to specify an alternative postgres database for testing, or an in memory database (if that is possible).

Code

class   User
extends MySpecs
with    OneAppPerTest
{
    override def newAppForTest( testData: TestData ) = new GuiceApplicationBuilder()
        // Somehow bind a database here, I guess?
        .build()

    "A test" should "test" in
    {
        val result = Application.instanceCache[api.controller.User]
            .apply( app )
            .list()( FakeRequest() )

        ...
    }
}

Stacktrace

[info] - should return an entity *** FAILED ***
[info]   com.google.inject.ConfigurationException: Guice configuration errors:
[info] 
[info] 1) No implementation for play.api.db.slick.DatabaseConfigProvider was bound.
[info]   while locating play.api.db.slick.DatabaseConfigProvider
[info]     for parameter 1 at api.controller.User.<init>(User.scala:22)
[info]   while locating api.controller.User
[info] 
[info] 1 error
[info]   at com.google.inject.internal.InjectorImpl.getProvider(InjectorImpl.java:1042)
[info]   at com.google.inject.internal.InjectorImpl.getProvider(InjectorImpl.java:1001)
[info]   at com.google.inject.internal.InjectorImpl.getInstance(InjectorImpl.java:1051)
[info]   at play.api.inject.guice.GuiceInjector.instanceOf(GuiceInjectorBuilder.scala:321)
[info]   at play.api.inject.guice.GuiceInjector.instanceOf(GuiceInjectorBuilder.scala:316)
[info]   at play.api.Application$$anonfun$instanceCache$1.apply(Application.scala:234)
[info]   at play.api.Application$$anonfun$instanceCache$1.apply(Application.scala:234)
[info]   at play.utils.InlineCache.fresh(InlineCache.scala:69)
[info]   at play.utils.InlineCache.apply(InlineCache.scala:55)
[info]   ...
Taig
  • 6,718
  • 4
  • 44
  • 65

2 Answers2

7

You need to add a configuration to your GuiceApplicationBuilder(), then everything should be handled automatically by play framework. Something like this should help:

val app = new GuiceApplicationBuilder()
        .configure(
            Configuration.from(
                Map(
                    "slick.dbs.YOURDBNAME.driver" -> "slick.driver.H2Driver$",
                    "slick.dbs.YOURDBNAME.db.driver" -> "org.h2.Driver",
                    "slick.dbs.YOURDBNAME.db.url" -> "jdbc:h2:mem:",

                    "slick.dbs.default.driver" -> "slick.driver.MySQLDriver$",
                    "slick.dbs.default.db.driver" -> "com.mysql.jdbc.Driver"
                )
            )
        )
        .in(Mode.Test)
        .build()
Mateusz Dymczyk
  • 14,969
  • 10
  • 59
  • 94
2

There is a little bit of setting up in this approach but the final result is fair. First of all start with implementing your own GuiceApplicationLoader by extending it. See my answer how to implement it. Why your own application loader? You can specify different configs/modules per Prod/Dev/Test modes - as well as different data sources. Your main application.conf wouldn't have data source configured. Instead, you would move it to environment specific configurations which would be merged with main configuration by application loader anyway. Your dev.conf would look as follows:

slick.dbs {
  default {
    driver = "slick.driver.PostgresDriver$",
    db {
      driver = "org.postgresql.Driver",
      url = "jdbc:postgresql://localhost:5432/dev",
      user = "postgres"
      password = "postgres"
    }
  }
}

And the trick now is to use same data source name, in this case default, for all other configurations (the database url, drivers, credentials etc. would be different). With such setup, your evolutions will be applied to your test and dev database. Your test.conf could look as follows:

slick.dbs {
  default {
    // in memory configuration
  }
}

In your tests, use WithApplicationLoader with your custom application loader instead and thats it.

@RunWith(classOf[JUnitRunner])
class ApplicationSpec extends Specification {

    "Application" should {

        "return text/html ok for home" in new WithApplicationLoader(new CustomApplicationLoader) {
          val home = route(FakeRequest(routes.ApplicationController.home())).get
          status(home) must equalTo(OK)
          contentType(home) must beSome.which(_ == "text/html")
        }

    }

}

Within the test itself, you have an access to app: Application value:

val service = app.injector.instanceOf(classOf[IService])
Mon Calamari
  • 4,403
  • 3
  • 26
  • 44
  • how do you decouple test code from prod code then? For instance what if I want to have some mock classes in `TestModule`? Since your `CustomApplicationLoader` will be in production code won't it be a problem? – Mateusz Dymczyk Aug 18 '15 at 10:24
  • `CustomApplicationLoader` will run in `Test` mode in tests with `test.conf` and `TestModule` installed. – Mon Calamari Aug 18 '15 at 10:31
  • I am aware of that, but you are using `new TestModule()` in your `CustomApplicationLoader` which means that all the classes that are used in `TestModule` have to be accessible from production code, a lot of people (including myself) don't like to mix test and prod code. – Mateusz Dymczyk Aug 18 '15 at 11:23
  • I inject my dependencies by trait (interface) and I have an abstract base module from all three modules extend where I bind shared implementations. so `CommonModule` contains shared implementations, `TestModule` test specific and `ProdModule` prod specific. – Mon Calamari Aug 18 '15 at 11:30
  • Yes but if `TestModule` contains all the test only classes and you do `new TestModule()` in `CustomApplicationLoader` which is in your `src` then you have to also put all the test only classes that are used by `TestModule` inside `src` and not `test` folders, right? There has to be a coupling between `test` and `src` folders then or am I missing something? – Mateusz Dymczyk Aug 18 '15 at 11:37
  • I see your point now, basically you do not like **shipping** prod code with test code, is it right? – Mon Calamari Aug 18 '15 at 11:39
  • yes exactly, I try to separate my prod and test code as much as possible unless there is a good reason not to (or there is no other way) – Mateusz Dymczyk Aug 18 '15 at 11:40
  • Nothing forbids you from implementing `TestApplicationLoader`, wiring up dependencies yourself and keeping all classes and files under `test` directory. `GuiceApplicationLoader` is designed for extension. – Mon Calamari Aug 18 '15 at 12:28
  • True but then I can just use `GuiceApplicationBuilder`, all those loaders seem like a bit of an overhead for such a simple thing – Mateusz Dymczyk Aug 18 '15 at 12:50
  • The point is to centralize test configuration and test dependencies. – Mon Calamari Aug 18 '15 at 12:55
  • yes but my point is, if we do split them into 2 loaders then do we really need it? Making a trait with `newAppForTest` and using `GuiceApplicationBuilder` will always save us a few lines of code and give the same result, won't it? That's what I'm looking for - a reason to actually implement `GuiceApplicationLoader`. It would be reasonable if we were to do it as in your other answer with all the modes in one place but as I pointed out above some might find that to be a bad practice. – Mateusz Dymczyk Aug 18 '15 at 13:07
  • If you are worried about test and prod source code getting mixed, just exclude it from packaging. http://stackoverflow.com/questions/20491505/how-to-exclude-resources-during-packaging-with-sbt-but-not-during-testing – Mon Calamari Aug 19 '15 at 07:43