Created
February 17, 2019 21:33
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//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