10

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

ps0604
  • 1,227
  • 23
  • 133
  • 330
  • Struggling with exact same issue => http://stackoverflow.com/questions/41369252/scala-play-and-scalatest-directory-structure-mismatch – SkyWalker Dec 28 '16 at 21:55
  • `DatabaseConfigProvider.get[JdbcProfile]("mydb")` ? – insan-e Jan 09 '17 at 10:57
  • @insan-e this throws an error: `type mismatch; found : slick.backend.DatabaseConfig[slick.driver.JdbcProfile] required: play.api.db.slick.DatabaseConfigProvider` – ps0604 Jan 09 '17 at 17:09

2 Answers2

8

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.

insan-e
  • 3,883
  • 3
  • 18
  • 43
  • In code sample you use OneAppPerSuite, it it possible to run multiple test suites with one app? – Slow Harry Jan 13 '17 at 06:12
  • I haven't tried it, but it should work... You'll have to manually group those suites (and put `@DoNotDiscover`), look at this example: http://stackoverflow.com/questions/15423337/doing-something-before-or-after-all-scalatest-tests – insan-e Jan 13 '17 at 08:48
  • my doubt is, does it require actual database that should be running all time ? Actually I am looking a way where testcase should automatically embed postgres db and use it – thedevd Mar 26 '21 at 08:57
3

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).

Paul Dolega
  • 2,446
  • 14
  • 23
  • your first solution is not working, it's throwing `type mismatch; found : slick.backend.DatabaseConfig[slick.driver.JdbcProfile] required: play.api.db.slick.DatabaseConfigProvider` – ps0604 Jan 09 '17 at 17:11
  • With regard to your second solution, I could configure the db in `application.conf`, however how do I inject the db configuration in the dao? keep in mind that I am using ScalaTest – ps0604 Jan 09 '17 at 17:20
  • You have full answer now, you were right there was a mistake, now it will work like a charm. – Paul Dolega Jan 09 '17 at 22:52
  • Pawel, both answers work, unfortunately I can only accept one. – ps0604 Jan 09 '17 at 23:23