Search code examples
scalaunit-testingplayframeworkmockingimplicit

Is it possible to mock DB connection in play functional testing and how?


Some of my controllers rely on DB connection and structured as follows:

def getAll(revId: Muid) = Action { implicit request =>
        DB.withConnection { implicit connection =>
...

I am trying to create a unit test for it with all mocked dependencies, including the connection as well. Now, injection of dependencies is easily done through Guice. However, I am struggling to find a way to mock implicit connection. And, eventually, the test is trying to connect to my default DB in test.

Is it even possible to mock implicits, given this situation, and how?

UPDATE

So, after playing with this thing for a while, I got the following: My class under test:

class ChecklistCreationScheduler @Inject()(jobScheduler: JobScheduler,
                                           dBApi: DBApi,
                                           futureChecklistRepository: FutureChecklistRepository) extends ClassLogger{
def scheduleSingleFutureChecklistJob(futureChecklistId: Muid): Unit = {
    logger.info(s"Preparing to schedule one time future checklist job for future checklist id '${futureChecklistId.uuid}'")
    val db = dBApi.database("default")
    logger.info("Database" + db)
    db.withConnection { implicit connection =>
      logger.info("Connection" + connection)
      ...
    }
}
}

And the test:

"ChecklistCreationScheduler#scheduleSingleFutureChecklistJob" should {
      "schedule a single job through a scheduler" in {
        val futureChecklistId = Muid.random()

        val jobScheduler = mock[JobScheduler]

        val connection = mock[Connection]
        val DB = mock[Database]
        DB.getConnection returns connection

        val dbApi = mock[DBApi]
        when(dbApi.database("default")).thenReturn(DB)

        val futureChecklistRepository = mock[FutureChecklistRepository]
        doReturn(Option.empty).when(futureChecklistRepository).getById(futureChecklistId)(connection)

        val chCreationScheduler = new ChecklistCreationScheduler(jobScheduler, dbApi, futureChecklistRepository)

        chCreationScheduler.scheduleSingleFutureChecklistJob(futureChecklistId) must throwA[UnexpectedException]
      }
    }

When I execute the test, it seems like the execution does not even get into the block of withConnection. (I am never getting to this line: logger.info("Connection" + connection)).

Any idea?


Solution

  • Here is how you can use Dependency Injection to easily mock the database call:

    Considering this is your controller:

    package controllers
    
    import javax.inject.Inject
    
    import play.api._
    import play.api.db.Database
    import play.api.mvc._
    
    class Application @Inject() (database: Database) extends Controller {
    
      def index = Action { implicit request =>
        database.withConnection { implicit  connection =>
          ???
        }
        Ok(views.html.index("Your new application is ready."))
      }
    }
    

    You can write a Specification like this:

    import java.sql.Connection
    
    import controllers.Application
    import org.specs2.mutable._
    import org.specs2.runner._
    import org.junit.runner._
    import org.specs2.mock._
    import play.api.db.Database
    import play.api.mvc.RequestHeader
    
    @RunWith(classOf[JUnitRunner])
    class ApplicationSpec extends Specification with Mockito {
    
      "Application" should {
    
        "index page" in {
          val connection = mock[Connection]
          val database = mock[Database]
          database.getConnection returns connection
    
          val controller = new Application(database)
    
          // You will also need to mock the request
          // so that you can add the expected behavior 
          val request = mock[RequestHeader]
          val result = controller.index(request)
    
          // do some assert about your result
          result must not beNull
        }
      }
    }
    

    Of course, some other mocks may be necessary to handle your (whole) use case.