├── .gitignore ├── .travis.yml ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── logo.png ├── publish.sh ├── settings.gradle └── src ├── main └── scala │ ├── com │ └── dimafeng │ │ └── testcontainers │ │ ├── GenericContainer.scala │ │ ├── MySQLContainer.scala │ │ ├── SeleniumTestContainerSuite.scala │ │ └── TestContainer.scala │ └── org │ └── testcontainers │ └── containers │ └── TestContainerAccessor.scala └── test ├── resources └── docker-compose.yml └── scala └── com └── dimafeng └── testcontainers ├── ContainerSpec.scala └── integration ├── ComposeSpec.scala ├── GenericContainerSpec.scala ├── MysqlSpec.scala └── SeleniumSpec.scala /.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | build/ 3 | *.iml 4 | out 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | jdk: 4 | - oraclejdk8 5 | 6 | os: 7 | - linux 8 | 9 | branches: 10 | only: 11 | - master 12 | 13 | cache: 14 | directories: 15 | - $HOME/.gradle -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The project repository has moved to https://github.com/testcontainers/testcontainers-scala 2 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | jcenter() 4 | } 5 | 6 | dependencies { 7 | classpath 'com.github.ben-manes:gradle-versions-plugin:0.12.0' 8 | } 9 | } 10 | 11 | plugins { 12 | id 'nebula.provided-base' version '3.0.3' 13 | id 'net.researchgate.release' version '2.4.0' 14 | } 15 | 16 | group 'com.dimafeng' 17 | 18 | apply plugin: 'scala' 19 | apply plugin: 'nebula.provided-base' 20 | apply plugin: 'com.github.ben-manes.versions' 21 | apply plugin: 'maven' 22 | apply plugin: 'signing' 23 | 24 | 25 | sourceCompatibility = 1.8 26 | 27 | def scala = project.hasProperty("scala") ? project.getProperty("scala") : scalaVersion 28 | println("Run with scala: $scala") 29 | def scalaPostfix = scala.substring(0, scala.lastIndexOf(".")) 30 | 31 | def artifactId = "testcontainers-scala_$scalaPostfix" 32 | 33 | repositories { 34 | mavenCentral() 35 | } 36 | 37 | dependencies { 38 | compile("org.testcontainers:testcontainers:$testcontainersVersion") 39 | provided("org.scala-lang:scala-library:$scala", 40 | "org.seleniumhq.selenium:selenium-java:$seleniumVersion", 41 | "org.testcontainers:selenium:$testcontainersVersion", 42 | "org.slf4j:slf4j-simple:$slf4jVersion", 43 | "org.scalatest:scalatest_$scalaPostfix:$scalaTestVersion", 44 | "org.testcontainers:mysql:$testcontainersVersion") 45 | 46 | testCompile("junit:junit:4.12", 47 | "org.testcontainers:selenium:$testcontainersVersion", 48 | "mysql:mysql-connector-java:$mysqlConnectorVersion", 49 | "org.mockito:mockito-all:$mockitoVersion") 50 | } 51 | 52 | test { 53 | exclude 'com/dimafeng/testcontainers/integration/**' 54 | } 55 | 56 | task integrationTest(type: Test) { 57 | include 'com/dimafeng/testcontainers/integration/**' 58 | } 59 | 60 | task sourcesJar(type: Jar) { 61 | classifier = 'sources' 62 | from sourceSets.main.allSource 63 | } 64 | 65 | task javadocJar(type: Jar) { 66 | classifier = 'javadoc' 67 | from scaladoc 68 | } 69 | 70 | artifacts { 71 | archives javadocJar, sourcesJar 72 | } 73 | 74 | signing { 75 | sign configurations.archives 76 | } 77 | 78 | ext.ossrhUsernameValue = project.hasProperty("ossrhUsername") ? project.getProperty("ossrhUsername") : "" 79 | ext.ossrhPasswordValue = project.hasProperty("ossrhPassword") ? project.getProperty("ossrhPassword") : "" 80 | 81 | /** 82 | * Skip signArchives for travis-ci 83 | */ 84 | tasks.each { task -> 85 | if (!project.hasProperty("ossrhPassword") && "signArchives".equals(task.name)) { 86 | task.enabled = false 87 | } 88 | } 89 | 90 | uploadArchives { 91 | repositories { 92 | mavenDeployer { 93 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) } 94 | 95 | repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") { 96 | authentication(userName: ossrhUsernameValue, password: ossrhPasswordValue) 97 | } 98 | 99 | snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") { 100 | authentication(userName: ossrhUsernameValue, password: ossrhPasswordValue) 101 | } 102 | 103 | pom.artifactId = artifactId 104 | 105 | pom.project { 106 | name artifactId 107 | packaging 'jar' 108 | description 'Scala wrapper for testcontainers-java' 109 | url 'https://github.com/dimafeng/testcontainers-scala' 110 | 111 | scm { 112 | connection 'scm:git:git@github.com:dimafeng/testcontainers-scala.git' 113 | developerConnection 'scm:git:git@github.com:dimafeng/testcontainers-scala.git' 114 | url 'git@github.com:dimafeng/testcontainers-scala.git' 115 | } 116 | 117 | licenses { 118 | license { 119 | name 'The MIT License (MIT)' 120 | url 'https://opensource.org/licenses/MIT' 121 | } 122 | } 123 | 124 | developers { 125 | developer { 126 | name 'Dmitry Fedosov' 127 | email 'dimafeng@gmail.com' 128 | } 129 | } 130 | } 131 | } 132 | } 133 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | version = 0.4.1 2 | 3 | archivesBaseName = "testcontainers-scala" 4 | 5 | testcontainersVersion = 1.1.7 6 | scalaVersion = 2.12.1 7 | seleniumVersion = 2.53.0 8 | slf4jVersion = 1.7.21 9 | scalaTestVersion = 3.0.1 10 | mysqlConnectorVersion = 5.1.39 11 | mockitoVersion = 1.10.19 -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimafeng/testcontainers-scala/d198fb469f5473e9c17011551188fe8e78ebc2d4/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Dec 21 18:16:36 EET 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimafeng/testcontainers-scala/d198fb469f5473e9c17011551188fe8e78ebc2d4/logo.png -------------------------------------------------------------------------------- /publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | scalaVersions="2.11.8 2.12.1" 5 | 6 | for version in $scalaVersions 7 | do 8 | printf "\n========Run tests for scala $version==========\n" 9 | ./gradlew clean -Pscala=$version test integrationTest 10 | done 11 | 12 | ./gradlew release 13 | 14 | for version in $scalaVersions 15 | do 16 | printf "\n========Upload archives for scala $version==========\n" 17 | ./gradlew clean -Pscala=$version uploadArchives 18 | done -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'testcontainers-scala' 2 | 3 | -------------------------------------------------------------------------------- /src/main/scala/com/dimafeng/testcontainers/GenericContainer.scala: -------------------------------------------------------------------------------- 1 | package com.dimafeng.testcontainers 2 | 3 | import org.testcontainers.containers.wait.WaitStrategy 4 | import org.testcontainers.containers.{BindMode, GenericContainer => OTCGenericContainer} 5 | 6 | class GenericContainer(imageName: String, 7 | exposedPorts: Seq[Int] = Seq(), 8 | env: Map[String, String] = Map(), 9 | command: Seq[String] = Seq(), 10 | classpathResourceMapping: Seq[(String, String, BindMode)] = Seq(), 11 | waitStrategy: Option[WaitStrategy] = None 12 | ) extends SingleContainer[OTCGenericContainer[_]] { 13 | 14 | type OTCContainer = OTCGenericContainer[T] forSome {type T <: OTCGenericContainer[T]} 15 | override implicit val container: OTCContainer = new OTCGenericContainer(imageName) 16 | 17 | if (exposedPorts.nonEmpty) { 18 | container.withExposedPorts(exposedPorts.map(int2Integer): _*) 19 | } 20 | env.foreach(Function.tupled(container.withEnv)) 21 | if (command.nonEmpty) { 22 | container.withCommand(command: _*) 23 | } 24 | classpathResourceMapping.foreach(Function.tupled(container.withClasspathResourceMapping)) 25 | waitStrategy.foreach(container.waitingFor) 26 | } 27 | 28 | object GenericContainer { 29 | def apply(imageName: String, 30 | exposedPorts: Seq[Int] = Seq(), 31 | env: Map[String, String] = Map(), 32 | command: Seq[String] = Seq(), 33 | classpathResourceMapping: Seq[(String, String, BindMode)] = Seq(), 34 | waitStrategy: WaitStrategy = null) = 35 | new GenericContainer(imageName, exposedPorts, env, command, classpathResourceMapping, Option(waitStrategy)) 36 | } -------------------------------------------------------------------------------- /src/main/scala/com/dimafeng/testcontainers/MySQLContainer.scala: -------------------------------------------------------------------------------- 1 | package com.dimafeng.testcontainers 2 | 3 | import org.testcontainers.containers.{MySQLContainer => OTCMySQLContainer} 4 | 5 | class MySQLContainer(configurationOverride: Option[String] = None) extends SingleContainer[OTCMySQLContainer[_]] { 6 | 7 | type OTCContainer = OTCMySQLContainer[T] forSome {type T <: OTCMySQLContainer[T]} 8 | override val container: OTCContainer = new OTCMySQLContainer() 9 | configurationOverride.foreach(container.withConfigurationOverride) 10 | 11 | def driverClassName = container.getDriverClassName 12 | 13 | def jdbcUrl = container.getJdbcUrl 14 | 15 | def password = container.getPassword 16 | 17 | def testQueryString = container.getTestQueryString 18 | 19 | def username = container.getUsername 20 | } 21 | 22 | object MySQLContainer { 23 | def apply(configurationOverride: String = null) = new MySQLContainer(Option(configurationOverride)) 24 | } -------------------------------------------------------------------------------- /src/main/scala/com/dimafeng/testcontainers/SeleniumTestContainerSuite.scala: -------------------------------------------------------------------------------- 1 | package com.dimafeng.testcontainers 2 | 3 | import java.io.File 4 | 5 | import org.openqa.selenium.WebDriver 6 | import org.openqa.selenium.remote.DesiredCapabilities 7 | import org.scalatest.Suite 8 | import org.testcontainers.containers.BrowserWebDriverContainer 9 | 10 | 11 | trait SeleniumTestContainerSuite extends ForEachTestContainer { 12 | self: Suite => 13 | 14 | def desiredCapabilities: DesiredCapabilities 15 | def recordingMode: (BrowserWebDriverContainer.VncRecordingMode, File) = null 16 | 17 | val container = SeleniumContainer(desiredCapabilities, recordingMode) 18 | 19 | implicit def webDriver: WebDriver = container.webDriver 20 | } 21 | 22 | class SeleniumContainer(desiredCapabilities: Option[DesiredCapabilities] = None, 23 | recordingMode: Option[(BrowserWebDriverContainer.VncRecordingMode, File)] = None) extends SingleContainer[BrowserWebDriverContainer[_]] { 24 | require(desiredCapabilities.isDefined, "'desiredCapabilities' is required parameter") 25 | 26 | type OTCContainer = BrowserWebDriverContainer[T] forSome {type T <: BrowserWebDriverContainer[T]} 27 | override val container: OTCContainer = new BrowserWebDriverContainer() 28 | desiredCapabilities.foreach(container.withDesiredCapabilities) 29 | recordingMode.foreach(Function.tupled(container.withRecordingMode)) 30 | 31 | def password = container.getPassword 32 | 33 | def port = container.getPort 34 | 35 | def seleniumAddress = container.getSeleniumAddress 36 | 37 | def vncAddress = container.getVncAddress 38 | 39 | def webDriver = container.getWebDriver 40 | } 41 | 42 | object SeleniumContainer { 43 | def apply(desiredCapabilities: DesiredCapabilities = null, recordingMode: (BrowserWebDriverContainer.VncRecordingMode, File) = null) = 44 | new SeleniumContainer(Option(desiredCapabilities), Option(recordingMode)) 45 | } 46 | -------------------------------------------------------------------------------- /src/main/scala/com/dimafeng/testcontainers/TestContainer.scala: -------------------------------------------------------------------------------- 1 | package com.dimafeng.testcontainers 2 | 3 | import java.io.File 4 | import org.junit.runner.Description 5 | import org.scalatest._ 6 | import org.testcontainers.containers.traits.LinkableContainer 7 | import org.testcontainers.containers.{GenericContainer => OTCGenericContainer, DockerComposeContainer => OTCDockerComposeContainer, MySQLContainer => OTCMySQLContainer, FailureDetectingExternalResource, BrowserWebDriverContainer, TestContainerAccessor} 8 | import org.testcontainers.shaded.com.github.dockerjava.api.command.InspectContainerResponse 9 | import org.testcontainers.shaded.com.github.dockerjava.api.model.Bind 10 | import scala.collection.JavaConverters._ 11 | import scala.concurrent.Future 12 | 13 | trait ForEachTestContainer extends SuiteMixin { 14 | self: Suite => 15 | 16 | val container: Container 17 | 18 | implicit private val suiteDescription = Description.createSuiteDescription(self.getClass) 19 | 20 | abstract protected override def runTest(testName: String, args: Args): Status = { 21 | container.starting() 22 | try { 23 | val status = super.runTest(testName, args) 24 | afterStart() 25 | status match { 26 | case FailedStatus => container.failed(new RuntimeException(status.toString)) 27 | case _ => container.succeeded() 28 | } 29 | status 30 | } 31 | catch { 32 | case e: Throwable => 33 | container.failed(e) 34 | throw e 35 | } 36 | finally { 37 | try { 38 | beforeStop() 39 | } 40 | finally { 41 | container.finished() 42 | } 43 | } 44 | } 45 | 46 | def afterStart(): Unit = {} 47 | 48 | def beforeStop(): Unit = {} 49 | } 50 | 51 | trait ForAllTestContainer extends SuiteMixin { 52 | self: Suite => 53 | 54 | val container: Container 55 | 56 | implicit private val suiteDescription = Description.createSuiteDescription(self.getClass) 57 | 58 | abstract override def run(testName: Option[String], args: Args): Status = { 59 | container.starting() 60 | afterStart() 61 | try { 62 | super.run(testName, args) 63 | } finally { 64 | try { 65 | beforeStop() 66 | } 67 | finally { 68 | container.finished() 69 | } 70 | } 71 | } 72 | 73 | def afterStart(): Unit = {} 74 | 75 | def beforeStop(): Unit = {} 76 | } 77 | 78 | sealed trait Container { 79 | def finished()(implicit description: Description): Unit 80 | 81 | def failed(e: Throwable)(implicit description: Description): Unit 82 | 83 | def starting()(implicit description: Description): Unit 84 | 85 | def succeeded()(implicit description: Description): Unit 86 | } 87 | 88 | class DockerComposeContainer(composeFile: File, exposedService: Map[String, Int] = Map()) extends TestContainerProxy[OTCDockerComposeContainer[_]] { 89 | 90 | type OTCContainer = OTCDockerComposeContainer[T] forSome {type T <: OTCDockerComposeContainer[T]} 91 | override val container: OTCContainer = new OTCDockerComposeContainer(composeFile) 92 | exposedService.foreach(Function.tupled(container.withExposedService)) 93 | 94 | def getServiceHost = container.getServiceHost _ 95 | 96 | def getServicePort = container.getServicePort _ 97 | } 98 | 99 | object DockerComposeContainer { 100 | def apply(composeFile: File, exposedService: Map[String, Int] = Map()) = new DockerComposeContainer(composeFile, exposedService) 101 | } 102 | 103 | trait TestContainerProxy[T <: FailureDetectingExternalResource] extends Container { 104 | implicit val container: T 105 | 106 | override def finished()(implicit description: Description): Unit = TestContainerAccessor.finished(description) 107 | 108 | override def succeeded()(implicit description: Description): Unit = TestContainerAccessor.succeeded(description) 109 | 110 | override def starting()(implicit description: Description): Unit = TestContainerAccessor.starting(description) 111 | 112 | override def failed(e: Throwable)(implicit description: Description): Unit = TestContainerAccessor.failed(e, description) 113 | } 114 | 115 | abstract class SingleContainer[T <: OTCGenericContainer[_]] extends TestContainerProxy[T] { 116 | 117 | def binds: Seq[Bind] = container.getBinds.asScala 118 | 119 | def command: Seq[String] = container.getCommandParts 120 | 121 | def containerId: String = container.getContainerId 122 | 123 | def containerInfo: InspectContainerResponse = container.getContainerInfo 124 | 125 | def containerIpAddress: String = container.getContainerIpAddress 126 | 127 | def containerName: String = container.getContainerName 128 | 129 | def env: Seq[String] = container.getEnv.asScala 130 | 131 | def exposedPorts: Seq[Int] = container.getExposedPorts.asScala.map(_.intValue()) 132 | 133 | def extraHosts: Seq[String] = container.getExtraHosts.asScala 134 | 135 | import scala.concurrent.ExecutionContext.Implicits.global 136 | 137 | def image: Future[String] = Future { 138 | container.getImage.get() 139 | } 140 | 141 | def linkedContainers: Map[String, LinkableContainer] = container.getLinkedContainers.asScala.toMap 142 | 143 | def mappedPort(port: Int): Int = container.getMappedPort(port) 144 | 145 | def portBindings: Seq[String] = container.getPortBindings.asScala 146 | } 147 | 148 | class MultipleContainers[T <: Product] private(val _containers: T) extends Container { 149 | 150 | private def containersAsIterator = containers.productIterator.map(_.asInstanceOf[Container]) 151 | 152 | def containers = _containers 153 | 154 | override def finished()(implicit description: Description): Unit = containersAsIterator.foreach(_.finished()(description)) 155 | 156 | override def succeeded()(implicit description: Description): Unit = containersAsIterator.foreach(_.succeeded()(description)) 157 | 158 | override def starting()(implicit description: Description): Unit = containersAsIterator.foreach(_.starting()(description)) 159 | 160 | override def failed(e: Throwable)(implicit description: Description): Unit = containersAsIterator.foreach(_.failed(e)(description)) 161 | } 162 | 163 | object MultipleContainers { 164 | def apply[T <: Container](t: T) = 165 | new MultipleContainers(new Tuple1(t)) 166 | 167 | def apply[T1 <: Container, T2 <: Container](t1: T1, t2: T2) = 168 | new MultipleContainers((t1, t2)) 169 | 170 | def apply[T1 <: Container, T2 <: Container, T3 <: Container](t1: T1, t2: T2, t3: T3) = 171 | new MultipleContainers((t1, t2, t3)) 172 | 173 | def apply[T1 <: Container, T2 <: Container, T3 <: Container, T4 <: Container](t1: T1, t2: T2, t3: T3, t4: T4) = 174 | new MultipleContainers((t1, t2, t3, t4)) 175 | 176 | def apply[T1 <: Container, T2 <: Container, T3 <: Container, T4 <: Container, T5 <: Container](t1: T1, t2: T2, t3: T3, t4: T4, t5: T5) = 177 | new MultipleContainers((t1, t2, t3, t4, t5)) 178 | } -------------------------------------------------------------------------------- /src/main/scala/org/testcontainers/containers/TestContainerAccessor.scala: -------------------------------------------------------------------------------- 1 | package org.testcontainers.containers 2 | 3 | import org.junit.runner.Description 4 | 5 | object TestContainerAccessor { 6 | def finished[T <:FailureDetectingExternalResource](description: Description)(implicit container: T): Unit = 7 | container.finished(description) 8 | 9 | def failed[T <:FailureDetectingExternalResource](e: Throwable, description: Description)(implicit container: T): Unit = 10 | container.failed(e, description) 11 | 12 | def starting[T <:FailureDetectingExternalResource](description: Description)(implicit container: T): Unit = 13 | container.starting(description) 14 | 15 | def succeeded[T <:FailureDetectingExternalResource](description: Description)(implicit container: T): Unit = 16 | container.succeeded(description) 17 | } 18 | -------------------------------------------------------------------------------- /src/test/resources/docker-compose.yml: -------------------------------------------------------------------------------- 1 | redis: 2 | image: redis 3 | elasticsearch: 4 | image: elasticsearch -------------------------------------------------------------------------------- /src/test/scala/com/dimafeng/testcontainers/ContainerSpec.scala: -------------------------------------------------------------------------------- 1 | package com.dimafeng.testcontainers 2 | 3 | import org.junit.runner.{Description, RunWith} 4 | import org.mockito.Matchers.any 5 | import org.mockito.{Matchers, Mockito} 6 | import org.mockito.Mockito.{times, verify} 7 | import org.scalatest.mockito.MockitoSugar._ 8 | import org.scalatest.{Reporter, Args, FlatSpec} 9 | import org.scalatest.junit.JUnitRunner 10 | import org.testcontainers.containers.{GenericContainer => OTCGenericContainer} 11 | import com.dimafeng.testcontainers.ContainerSpec._ 12 | 13 | @RunWith(classOf[JUnitRunner]) 14 | class ContainerSpec extends FlatSpec { 15 | behavior of "ForEachTestContainer" 16 | 17 | it should "call all appropriate methods of the container" in { 18 | val container = Mockito.mock(classOf[SampleOTCContainer]) 19 | 20 | new TestSpec({ 21 | assert(1 == 1) 22 | }, new SampleContainer(container)).run(None, Args(mock[Reporter])) 23 | 24 | verify(container).starting(any()) 25 | verify(container, times(0)).failed(any(), any()) 26 | verify(container).finished(any()) 27 | verify(container).succeeded(any()) 28 | } 29 | 30 | it should "call all appropriate methods of the container if assertion fails" in { 31 | val container = Mockito.mock(classOf[SampleOTCContainer]) 32 | 33 | new TestSpec({ 34 | assert(1 == 2) 35 | }, new SampleContainer(container)).run(None, Args(mock[Reporter])) 36 | 37 | verify(container).starting(any()) 38 | verify(container).failed(any(), any()) 39 | verify(container).finished(any()) 40 | verify(container, times(0)).succeeded(any()) 41 | } 42 | 43 | it should "call all appropriate methods of the multiple containers" in { 44 | val container1 = Mockito.mock(classOf[SampleOTCContainer]) 45 | val container2 = Mockito.mock(classOf[SampleOTCContainer]) 46 | 47 | val containers = MultipleContainers(new SampleContainer(container1), new SampleContainer(container2)) 48 | 49 | new TestSpec({ 50 | assert(1 == 1) 51 | }, containers).run(None, Args(mock[Reporter])) 52 | 53 | verify(container1).starting(any()) 54 | verify(container1, times(0)).failed(any(), any()) 55 | verify(container1).finished(any()) 56 | verify(container1).succeeded(any()) 57 | verify(container2).starting(any()) 58 | verify(container2, times(0)).failed(any(), any()) 59 | verify(container2).finished(any()) 60 | verify(container2).succeeded(any()) 61 | } 62 | 63 | it should "start and stop container only once" in { 64 | val container = Mockito.mock(classOf[SampleOTCContainer]) 65 | 66 | new MultipleTestsSpec({ 67 | assert(1 == 1) 68 | }, new SampleContainer(container)).run(None, Args(mock[Reporter])) 69 | 70 | verify(container).starting(any()) 71 | verify(container, times(0)).failed(any(), any()) 72 | verify(container).finished(any()) 73 | verify(container, times(0)).succeeded(any()) 74 | } 75 | 76 | it should "call afterStart() and beforeStop()" in { 77 | val container = Mockito.mock(classOf[SampleOTCContainer]) 78 | 79 | // ForEach 80 | val specForEach = Mockito.spy(new TestSpec({}, new SampleContainer(container))) 81 | specForEach.run(None, Args(mock[Reporter])) 82 | 83 | verify(specForEach).afterStart() 84 | verify(specForEach).beforeStop() 85 | 86 | // ForAll 87 | 88 | val specForAll = Mockito.spy(new MultipleTestsSpec({}, new SampleContainer(container))) 89 | specForAll.run(None, Args(mock[Reporter])) 90 | 91 | verify(specForAll).afterStart() 92 | verify(specForAll).beforeStop() 93 | } 94 | } 95 | 96 | object ContainerSpec { 97 | 98 | private class TestSpec(testBody: => Unit, _container: Container) extends FlatSpec with ForEachTestContainer { 99 | override val container = _container 100 | 101 | it should "test" in { 102 | testBody 103 | } 104 | } 105 | 106 | private class MultipleTestsSpec(testBody: => Unit, _container: Container) extends FlatSpec with ForAllTestContainer { 107 | override val container = _container 108 | 109 | it should "test1" in { 110 | testBody 111 | } 112 | 113 | it should "test2" in { 114 | testBody 115 | } 116 | } 117 | 118 | private class SampleOTCContainer extends OTCGenericContainer { 119 | override def starting(description: Description): Unit = {} 120 | 121 | override def failed(e: Throwable, description: Description): Unit = {} 122 | 123 | override def finished(description: Description): Unit = {} 124 | 125 | override def succeeded(description: Description): Unit = {} 126 | } 127 | 128 | private class SampleContainer(sampleOTCContainer: SampleOTCContainer) extends SingleContainer[SampleOTCContainer] { 129 | override implicit val container: SampleOTCContainer = sampleOTCContainer 130 | } 131 | } -------------------------------------------------------------------------------- /src/test/scala/com/dimafeng/testcontainers/integration/ComposeSpec.scala: -------------------------------------------------------------------------------- 1 | package com.dimafeng.testcontainers.integration 2 | 3 | import java.io.File 4 | 5 | import com.dimafeng.testcontainers.{DockerComposeContainer, ForAllTestContainer} 6 | import org.scalatest.FlatSpec 7 | 8 | class ComposeSpec extends FlatSpec with ForAllTestContainer { 9 | override val container = DockerComposeContainer(new File("src/test/resources/docker-compose.yml"), exposedService = Map("redis_1" -> 6379)) 10 | 11 | "DockerComposeContainer" should "retrieve non-0 port for any of services" in { 12 | assert(container.getServicePort("redis_1", 6379) > 0) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/test/scala/com/dimafeng/testcontainers/integration/GenericContainerSpec.scala: -------------------------------------------------------------------------------- 1 | package com.dimafeng.testcontainers.integration 2 | 3 | import java.net.URL 4 | 5 | import com.dimafeng.testcontainers.{ForAllTestContainer, GenericContainer} 6 | import org.junit.runner.RunWith 7 | import org.scalatest.FlatSpec 8 | import org.scalatest.junit.JUnitRunner 9 | import org.testcontainers.containers.wait.Wait 10 | 11 | import scala.io.Source 12 | 13 | @RunWith(classOf[JUnitRunner]) 14 | class GenericContainerSpec extends FlatSpec with ForAllTestContainer { 15 | override val container = GenericContainer("nginx:latest", 16 | exposedPorts = Seq(80), 17 | waitStrategy = Wait.forHttp("/") 18 | ) 19 | 20 | "GenericContainer" should "start nginx and expose 80 port" in { 21 | assert(Source.fromInputStream( 22 | new URL(s"http://${container.containerIpAddress}:${container.mappedPort(80)}/").openConnection().getInputStream 23 | ).mkString.contains("If you see this page, the nginx web server is successfully installed")) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/test/scala/com/dimafeng/testcontainers/integration/MysqlSpec.scala: -------------------------------------------------------------------------------- 1 | package com.dimafeng.testcontainers.integration 2 | 3 | import java.sql.DriverManager 4 | 5 | import com.dimafeng.testcontainers.{ForAllTestContainer, MySQLContainer} 6 | import org.junit.runner.RunWith 7 | import org.scalatest.FlatSpec 8 | import org.scalatest.junit.JUnitRunner 9 | 10 | @RunWith(classOf[JUnitRunner]) 11 | class MysqlSpec extends FlatSpec with ForAllTestContainer { 12 | 13 | override val container = MySQLContainer() 14 | 15 | "Mysql container" should "be started" in { 16 | Class.forName(container.driverClassName) 17 | val connection = DriverManager.getConnection(container.jdbcUrl, container.username, container.password) 18 | 19 | val prepareStatement = connection.prepareStatement("select 1") 20 | try { 21 | val resultSet = prepareStatement.executeQuery() 22 | resultSet.next() 23 | assert(1 == resultSet.getInt(1)) 24 | resultSet.close() 25 | } finally { 26 | prepareStatement.close() 27 | } 28 | 29 | connection.close() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/scala/com/dimafeng/testcontainers/integration/SeleniumSpec.scala: -------------------------------------------------------------------------------- 1 | package com.dimafeng.testcontainers.integration 2 | 3 | import com.dimafeng.testcontainers.SeleniumTestContainerSuite 4 | import org.junit.runner.RunWith 5 | import org.openqa.selenium.remote.DesiredCapabilities 6 | import org.scalatest.FlatSpec 7 | import org.scalatest.junit.JUnitRunner 8 | import org.scalatest.selenium.WebBrowser 9 | 10 | @RunWith(classOf[JUnitRunner]) 11 | class SeleniumSpec extends FlatSpec with SeleniumTestContainerSuite with WebBrowser { 12 | override def desiredCapabilities = DesiredCapabilities.chrome() 13 | 14 | "Browser" should "show google" in { 15 | go to "http://google.com" 16 | } 17 | } 18 | --------------------------------------------------------------------------------