Skip to content

Instantly share code, notes, and snippets.

@ptrdom
Created February 17, 2019 21:33
Show Gist options
  • Save ptrdom/fb98f55e45c6e591d36ce09b9b19ed04 to your computer and use it in GitHub Desktop.
Save ptrdom/fb98f55e45c6e591d36ce09b9b19ed04 to your computer and use it in GitHub Desktop.
Example of Play Framework + Testcontainers usage with isolated Slick - not using play-slick
//Test tag
case object DockerDatabaseTest extends Tag("DockerDatabase")
//Test harness
package utilities.database
import com.dimafeng.testcontainers.{ForAllTestContainer, PostgreSQLContainer}
import org.scalatest.mockito.MockitoSugar
import org.scalatest.{Assertion, Suite}
import org.scalatestplus.play.AppProvider
import play.api.db.evolutions.Evolutions
import play.api.db.{Database, Databases}
import scala.concurrent.{ExecutionContext, Future}
import scala.util.control.NonFatal
//Since 'ForAllTestContainer' is used, database evolutions downs must completely clean database
//otherwise test data will carry over between tests in a suite. 'ForEachTestContrainer' could be used,
//but connections in pool get corrupted and cannot be used after first test in suite runs.
//Consider fixing this issue, maybe by pool recreation between tests, and then change to 'ForEachTestContrainer'.
trait PlayPostgreSQLTest extends Suite with AppProvider with ForAllTestContainer with MockitoSugar {
override val container: PostgreSQLContainer
def applyEvolutions(): Unit = {
withDatabase { database =>
Evolutions.applyEvolutions(database, autocommit = false)
}
}
def unapplyEvolutions(): Unit = {
withDatabase { database =>
Evolutions.cleanupEvolutions(database, autocommit = false)
}
}
def withEvolutions(assertionFun: () => Assertion): Assertion = {
applyEvolutions()
val assertion = assertionFun.apply()
unapplyEvolutions()
assertion
}
def withEvolutions(futureAssertionFun: () => Future[Assertion])(implicit ec: ExecutionContext): Future[Assertion] = {
applyEvolutions()
futureAssertionFun
.apply()
.map {
assertion =>
unapplyEvolutions()
assertion
}
}
private def withDatabase(block: (Database) => Unit) = {
val database = Databases(
driver = "org.postgresql.Driver",
url = container.jdbcUrl,
name = "default",
config = Map(
"username" -> container.username,
"password" -> container.password
)
)
try {
val result = block(database)
database.shutdown()
result
} catch {
case NonFatal(e) =>
database.shutdown()
throw e
}
}
}
//Mock Play application
package utilities
import com.dimafeng.testcontainers.PostgreSQLContainer
import com.google.inject.AbstractModule
import com.mohiva.play.silhouette.api.{Silhouette, SilhouetteProvider}
import com.mohiva.play.silhouette.api.repositories.AuthInfoRepository
import com.mohiva.play.silhouette.api.util.PasswordHasherRegistry
import modules.authentication.DefaultEnv
import modules.system.startup.StartUp
import net.codingwell.scalaguice.ScalaModule
import org.scalatest.mockito.MockitoSugar
import play.api.{Application, Configuration, Mode}
import play.api.inject.bind
import play.api.inject.guice.GuiceApplicationBuilder
object TestApplications extends MockitoSugar {
def basicDatabaseTestApplication(container: PostgreSQLContainer): Application = {
class FakeSilhouetteModule extends AbstractModule with ScalaModule {
override def configure(): Unit = {
bind[Silhouette[DefaultEnv]].toInstance(mock[SilhouetteProvider[DefaultEnv]])
bind[AuthInfoRepository].toInstance(mock[AuthInfoRepository])
bind[PasswordHasherRegistry].toInstance(mock[PasswordHasherRegistry])
}
}
val configuration: Configuration = Configuration.from(
Map(
"db.default.driver" -> "org.postgresql.Driver",
"db.default.url" -> container.jdbcUrl,
"db.default.username" -> container.username,
"db.default.password" -> container.password,
"test.dataSourceClass" -> "org.postgresql.ds.PGSimpleDataSource",
"test.numThreads" -> "10",
"test.queueSize" -> "1000",
"test.properties.url" -> container.jdbcUrl,
"test.properties.user" -> container.username,
"test.properties.password" -> container.password
)
)
GuiceApplicationBuilder(configuration = configuration)
.overrides(new FakeSilhouetteModule)
.overrides(bind[StartUp].toInstance(mock[StartUp]))
.in(Mode.Test)
.build()
}
}
// Test Suite
package test
import com.dimafeng.testcontainers.PostgreSQLContainer
import org.scalatest.concurrent.{IntegrationPatience, ScalaFutures}
import org.scalatest.mockito.MockitoSugar
import org.scalatest.{BeforeAndAfterEach, TestData}
import org.scalatestplus.play.PlaySpec
import org.scalatestplus.play.guice.GuiceOneAppPerTest
import play.api.Application
import utilities.TestApplications._
import utilities.database.PlayPostgreSQLTest
import utilities.tag.DockerDatabaseTest
import scala.concurrent.{ExecutionContext, Future}
class SomeImplementationSpec extends PlaySpec with GuiceOneAppPerTest with ScalaFutures with IntegrationPatience with PlayPostgreSQLTest with MockitoSugar with BeforeAndAfterEach {
override val container: PostgreSQLContainer = PostgreSQLContainer("postgres:10.5-alpine")
override def newAppForTest(testData: TestData): Application = {
basicDatabaseTestApplication(container)
}
"An implementation" when {
"it has to do stuff" should {
"should do it well" taggedAs DockerDatabaseTest in {
implicit lazy val executionContext = app.injector.instanceOf[ExecutionContext]
withEvolutions { () =>
// Test assertion goes here
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment