Injecting PlaySlick database connection in ScalaTest

1.5k views Asked by At

I have the following DAO that connects to a database using PlaySlick. The class has a method read that I need to test with ScalaTest. My problem is that I don't know how to mock DatabaseConfigProvider to inject it in the UsersDAO class and test the read method. This is the class to test:

class UsersDAO @Inject()(@NamedDatabase("mydb") 
          protected val dbConfigProvider: DatabaseConfigProvider)
          extends with HasDatabaseConfigProvider[JdbcProfile] {

   import driver.api._

   val db1 = dbConfigProvider.get[JdbcProfile].db

   def read (sk: Int) = {
      val users = TableQuery[UserDB]
      val action = users.filter(_.sk === sk).result
      val future = db1.run(action.asTry)
      future.map{
        case Success(s) => 
          if (s.length>0)
            Some(s(0))
          else
            None
        case Failure(e) => throw new Exception ("Failure: " + e.getMessage)
      }
   }

}

and this is my attempt to write the test:

class UserDAOTest extends PlaySpec with OneAppPerSuite  {

  implicit override lazy val app = new GuiceApplicationBuilder().
  configure(
            Configuration.from(
                Map(
                    "slick.dbs.mydb.driver" -> "slick.driver.MySQLDriver$",
                    "slick.dbs.mydb.db.driver" -> "com.mysql.jdbc.Driver",
                    "slick.dbs.mydb.db.url" -> "jdbc:mysql://localhost:3306/control",
                    "slick.dbs.mydb.db.user" -> "root",
                    "slick.dbs.mydb.db.password" -> "xxxxx"
                )
            )
        ).build

  val dbConfigProvider = app.injector.instanceOf[DatabaseConfigProvider]

  "Example " should {
    "be valid" in {

      val controller = new UsersDAO(dbConfigProvider)
      val result = controller.read(1)
      println(result) 
    }
  }

When I run the test it fails with the following error message:

com.google.inject.ConfigurationException: Guice configuration errors:

1) No implementation for play.api.db.slick.DatabaseConfigProvider was bound. while locating play.api.db.slick.DatabaseConfigProvider

2

There are 2 answers

3
insan-e On BEST ANSWER

IMHO, it is best not to interfere with Play's injection stuff, but to just use it. This should work:

class UserDAOTest extends PlaySpec with OneAppPerSuite with ScalaFutures {

  implicit override lazy val app = new GuiceApplicationBuilder().
    configure(
      "slick.dbs.mydb.driver" -> "slick.driver.MySQLDriver$",
      "slick.dbs.mydb.db.driver" -> "com.mysql.jdbc.Driver",
      "slick.dbs.mydb.db.url" -> "jdbc:mysql://localhost:3306/control",
      "slick.dbs.mydb.db.user" -> "root",
      "slick.dbs.mydb.db.password" -> "xxxxx").build

  def userDAO(implicit app: Application): UserDAO = Application.instanceCache[UserDAO].apply(app)

  "UserDAO" should {
    "do whatever" in {
      whenReady(userDAO.read(1)) { res =>
        println(res)
      }
    }
  }

}

I've updated my repo in case I missed something.

4
Paul Dolega On

I am not sure you want to mock database altogether from your tests or simply use different database configuration.

Looking at your code sample I assume the latter.

If you really would like to do it as you started, the simplest solution would be this:

// instead of your line below
//  val dbConfigProvider = app.injector.instanceOf[DatabaseConfigProvider]
// use this:
   val userDao = app.injector.instanceOf[UsersDao]

Above will inject your DAO and implicitly deal with your DatabaseConfigProvider.

But I don't understand here one thing - why don't you just create another configuration (application.conf) in your test resources with contents like this:

slick.dbs.mydb.driver="slick.driver.MySQLDriver$"
slick.dbs.mydb.db.driver="com.mysql.jdbc.Driver"
slick.dbs.mydb.db.url = "jdbc:mysql://localhost:3306/control"
slick.dbs.mydb.db.user=root
slick.dbs.mydb.db.password="xxxxx"

After doing this just change your app creation to this:

implicit override lazy val app = new GuiceApplicationBuilder().build

and just normally inject UsersDao like this (same way as above):

val usersDao = app.injector.instanceOf[UsersDao]

?

(I am assuming above that you are just having different db configuration in test and in app).

Full code

test/resource/application.conf

slick.dbs.mydb.driver="slick.driver.MySQLDriver$"
slick.dbs.mydb.db.driver="com.mysql.jdbc.Driver"
slick.dbs.mydb.db.url = "jdbc:mysql://localhost:3306/control"
slick.dbs.mydb.db.user=root
slick.dbs.mydb.db.password="xxxxx"

UserDaoTest.scala

import scala.concurrent.Await
import scala.concurrent.duration.DurationInt
//... other imports

class UserDAOTest extends PlaySpec with OneAppPerSuite  {

  implicit override lazy val app = new GuiceApplicationBuilder().build

  val userDao = app.injector.instanceOf[UsersDao]

  "Example " should {
    "be valid" in {
      val result = userDao.read(1)
      println(Await.result(result), 1.second) 

      // would be better to use whenReady from org.scalatest.concurrent.Futures
      // but it's not really related to this question that much
    }
  }
}

Summary

I think that kind of a conclusion here would be to avoid constructing objects by hand (like in the first approach - creating UsersDao with constructor with parameter). If you don't need to do it (sometimes you do - when e.g. you want to change some of the parameters) it's most of the time easier to just delegate the whole object construction to DI (like - simply pull UsersDao with all dependencies already injected).