Scala wrapper for testcontainers-java that
allows using docker containers for functional/integration/unit testing.
TestContainers is a Java 8 library that supports JUnit tests, providing lightweight, throwaway instances of common databases, Selenium web browsers, or anything else that can run in a Docker container.
Testcontainers-scala in action: http://dimafeng.com/2016/08/01/testcontainers-selenium/
testcontainers-java is awesome and yes, you can use it in scala project but:
- It's written to be used in JUnit tests
DockerComposeContainer<SELF extends DockerComposeContainer<SELF>>
- it's not convinient to use its api with this 'recursive generic' from scala
Plus
- This wrapper provides with scala interfaces, approaches, types
- This wrapper is integrated with scalatest
Maven
<!-- Scala 2.11.* -->
<dependency>
<groupId>com.dimafeng</groupId>
<artifactId>testcontainers-scala_2.11</artifactId>
<version>0.16.0</version>
<scope>test</scope>
</dependency>
<!-- Scala 2.12.* -->
<dependency>
<groupId>com.dimafeng</groupId>
<artifactId>testcontainers-scala_2.12</artifactId>
<version>0.16.0</version>
<scope>test</scope>
</dependency>
Gradle
testCompile("com.dimafeng:testcontainers-scala_2.11:0.16.0") // Scala 2.11.*
testCompile("com.dimafeng:testcontainers-scala_2.12:0.16.0") // Scala 2.12.*
SBT
libraryDependencies += "com.dimafeng" %% "testcontainers-scala" % "0.16.0" % "test"
- JDK >= 1.8
- See 'Compatibility' section
There are two modes of container launching: ForEachTestContainer
and ForAllTestContainer
.
The first one starts a new container before each test case and then stops and removes it. The second one
starts and stops a container only once.
To start using it, you just need to extend one of those traits and override a container
val as follows:
import com.dimafeng.testcontainers.{ForAllTestContainer, MySQLContainer}
class MysqlSpec extends FlatSpec with ForAllTestContainer {
override val container = MySQLContainer()
it should "do something" in {
Class.forName(container.driverClassName)
val connection = DriverManager.getConnection(container.jdbcUrl, container.username, container.password)
...
}
}
This spec has a clean mysql database instance for each of its test cases.
import org.testcontainers.containers.MySQLContainer
class MysqlSpec extends FlatSpec with ForAllTestContainer {
override val container = MySQLContainer()
it should "do something" in {
...
}
it should "do something 2" in {
...
}
}
This spec starts one container and both tests share the container's state.
The most flexible but less convinient containtainer type is GenericContainer
. This container allows to launch any docker image
with custom configuration.
class GenericContainerSpec extends FlatSpec with ForAllTestContainer {
override val container = GenericContainer("nginx:latest",
exposedPorts = Seq(80),
waitStrategy = Wait.forHttp("/")
)
"GenericContainer" should "start nginx and expose 80 port" in {
assert(Source.fromInputStream(
new URL(s"http://${container.containerIpAddress}:${container.mappedPort(80)}/").openConnection().getInputStream
).mkString.contains("If you see this page, the nginx web server is successfully installed"))
}
}
class ComposeSpec extends FlatSpec with ForAllTestContainer {
override val container = DockerComposeContainer(new File("src/test/resources/docker-compose.yml"), exposedService = Map("redis_1" -> 6379))
"DockerComposeContainer" should "retrieve non-0 port for any of services" in {
assert(container.getServicePort("redis_1", 6379) > 0)
}
}
Before you can use this type of containers, you need to add the following dependencies to your project:
"org.seleniumhq.selenium" % "selenium-java" % "2.53.1"
and
"org.testcontainers" % "selenium" % "1.1.8"
Now you can write a test in this way:
class SeleniumSpec extends FlatSpec with SeleniumTestContainerSuite with WebBrowser {
override def desiredCapabilities = DesiredCapabilities.chrome()
"Browser" should "show google" in {
go to "http://google.com"
}
}
In this case, you'll obtain a clean instance of browser (firefox/chrome) within container to which a test will connect via remote-driver. See Webdriver Containers for more details.
Requires you to add this dependency
class MysqlSpec extends FlatSpec with ForAllTestContainer {
override val container = MySQLContainer()
"Mysql container" should "be started" in {
Class.forName(container.driverClassName)
val connection = DriverManager.getConnection(container.jdbcUrl, container.username, container.password)
...
}
}
The container can also be customized using the constructor parameters, this snippet will initialize a docker container from a specific docker image, with a specific schema name and specific username/password
class MysqlSpec extends FlatSpec with ForAllTestContainer {
override val container = MySQLContainer(mysqlImageVersion = "mysql:5.7.18",
databaseName = "testcontainer-scala",
username = "scala",
password = "scala")
"Mysql container" should "be started" in {
Class.forName(container.driverClassName)
val connection = DriverManager.getConnection(container.jdbcUrl, container.username, container.password)
...
}
}
Requires you to add this dependency
class PostgresqlSpec extends FlatSpec with ForAllTestContainer {
override val container = PostgreSQLContainer()
"PostgreSQL container" should "be started" in {
Class.forName(container.driverClassName)
val connection = DriverManager.getConnection(container.jdbcUrl, container.username, container.password)
...
}
}
...
val container = MultipleContainers(MySQLContainer(), GenericContainer(...))
// access to containers
containers.containers._1.containerId // container id of the first container
...
If configuration of one container depends on runtime state of another one, you can use LazyContainer
wrapper as follows:
lazy val container1 = Container1()
lazy val container2 = Container2(container1.port)
val containers = MultipleContainers(LazyContainer(pgContainer), LazyContainer(appContainer))
This container will allow you to map container ports to statically defined ports on the docker host.
...
val container = FixedHostPortGenericGenericContainer("nginx:latest",
waitStrategy = Wait.forHttp("/"),
exposedHostPort = 8090,
exposedContainerPort = 80
)
If you want to execute your code after container start or before container stop you can override afterStart()
and beforeStop()
methods.
class MysqlSpec extends FlatSpec with ForAllTestContainer {
...
override def beforeStop(): Unit = {
// your code
}
override def afterStart(): Unit = {
// your code
}
}
-
0.16.0
FixedHostPortGenericContainer
added
-
0.15.0
- Additional configuration parameters for
MySQLContainer
- Improvements to
MultipleContainers
- container lazy creation for dependent containers
- Additional configuration parameters for
-
0.14.0
- TestContainers
1.5.1
->1.6.0
- TestContainers
-
0.13.0
- TestContainers
1.4.3
->1.5.1
- Scala 2.10 support
- TestContainers
-
0.12.0
- Improvement:
afterStart
hook now handles exceptions correctly
- Improvement:
-
0.11.0
- Improvement: containers don't start in
ForAllTestContainer
if all tests are ignored
- Improvement: containers don't start in
-
0.10.0
- TestContainers
1.4.2
->1.4.3
- Fix of #8
- TestContainers
-
0.8.0
- PostgreSQL container
-
0.7.0
- TestContainers
1.2.1
->1.4.2
- TestContainers
-
0.6.0
- TestContainers
1.2.0
->1.2.1
- Fix of the
afterStart
hook
- TestContainers
-
0.5.0
- TestContainers
1.1.8
->1.2.0
- TestContainers
-
0.4.1
- TestContainers
1.1.7
->1.1.8
- TestContainers
-
0.4.0
- TestContainers
1.1.5
->1.1.7
- Scala cross-building (2.11.* + 2.12.*)
- TestContainers
-
0.3.0
- TestContainers
1.1.0
->1.1.5
- Start/Stop hooks
- TestContainers
-
0.2.0
- TestContainers
1.0.5
->1.1.0
- Code refactoring
- Scala wrappers for major container types
- TestContainers
$ sbt clean release