Testing Examples¶
Using docker-kotlin for integration testing.
JUnit 5 Integration¶
Basic Test Setup¶
import io.github.joshrotenberg.dockerkotlin.redis.RedisTemplate
import org.junit.jupiter.api.*
import kotlin.test.assertEquals
class RedisIntegrationTest {
companion object {
private lateinit var redis: RedisTemplate
@JvmStatic
@BeforeAll
fun setupClass() {
redis = RedisTemplate("test-redis") {
dynamicPort()
}
runBlocking {
redis.startAndWait()
}
}
@JvmStatic
@AfterAll
fun teardownClass() {
redis.close()
}
}
@Test
fun `should connect to redis`() = runBlocking {
val pong = redis.redisCommand("PING")
assertEquals("PONG", pong)
}
@Test
fun `should store and retrieve values`() = runBlocking {
redis.redisCommand("SET", "test-key", "test-value")
val value = redis.redisCommand("GET", "test-key")
assertEquals("test-value", value)
}
}
Using Extension¶
import org.junit.jupiter.api.extension.*
class DockerExtension : BeforeAllCallback, AfterAllCallback {
companion object {
private val containers = mutableMapOf<String, RedisTemplate>()
}
override fun beforeAll(context: ExtensionContext) {
val redis = RedisTemplate("test-${context.displayName}") {
dynamicPort()
}
runBlocking { redis.startAndWait() }
containers[context.uniqueId] = redis
}
override fun afterAll(context: ExtensionContext) {
containers.remove(context.uniqueId)?.close()
}
fun getRedis(context: ExtensionContext): RedisTemplate {
return containers[context.uniqueId]!!
}
}
@ExtendWith(DockerExtension::class)
class MyTest {
@Test
fun test(context: ExtensionContext) {
val redis = DockerExtension().getRedis(context)
// Use redis...
}
}
Per-Test Container¶
class IsolatedContainerTest {
private lateinit var redis: RedisTemplate
@BeforeEach
fun setup() {
redis = RedisTemplate("test-redis-${System.currentTimeMillis()}") {
dynamicPort()
}
runBlocking { redis.startAndWait() }
}
@AfterEach
fun teardown() {
redis.close()
}
@Test
fun `test with isolated container`() = runBlocking {
// Each test gets a fresh container
redis.redisCommand("SET", "key", "value")
}
}
Testing with Multiple Services¶
class FullStackTest {
companion object {
private lateinit var redis: RedisTemplate
private lateinit var postgres: ContainerId
private val docker = Docker()
@JvmStatic
@BeforeAll
fun setup() = runBlocking {
// Create network
NetworkCreateCommand("test-network").execute()
// Start Redis
redis = RedisTemplate("test-redis") {
network = "test-network"
networkAliases.add("redis")
dynamicPort()
}
redis.startAndWait()
// Start PostgreSQL
postgres = docker.run("postgres:16") {
name("test-postgres")
network("test-network")
networkAlias("database")
dynamicPort(5432)
env("POSTGRES_PASSWORD", "test")
env("POSTGRES_DB", "testdb")
detach()
}
// Wait for Postgres
waitForPostgres()
}
private suspend fun waitForPostgres() {
repeat(30) {
try {
val result = ExecCommand(postgres.value, "pg_isready").execute()
if (result.exitCode == 0) return
} catch (e: Exception) {}
delay(1000)
}
throw RuntimeException("PostgreSQL failed to start")
}
@JvmStatic
@AfterAll
fun teardown() = runBlocking {
redis.close()
docker.stop(postgres.value)
docker.rm(postgres.value)
NetworkRmCommand("test-network").execute()
}
}
@Test
fun `application connects to both services`() = runBlocking {
// Your integration test here
val redisUrl = redis.connectionString()
val postgresUrl = "postgresql://postgres:test@localhost:${getMappedPort(5432)}/testdb"
// Test application with these URLs
}
}
Conditional Test Execution¶
import org.junit.jupiter.api.condition.EnabledIf
class ConditionalDockerTest {
companion object {
@JvmStatic
fun isDockerAvailable(): Boolean {
return try {
ProcessBuilder("docker", "version")
.redirectErrorStream(true)
.start()
.waitFor() == 0
} catch (e: Exception) {
false
}
}
}
@Test
@EnabledIf("isDockerAvailable")
fun `test requires docker`() = runBlocking {
RedisTemplate("test-redis") {
dynamicPort()
}.use { redis ->
redis.startAndWait()
assertEquals("PONG", redis.redisCommand("PING"))
}
}
}
Parameterized Container Tests¶
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource
class VersionCompatibilityTest {
@ParameterizedTest
@ValueSource(strings = ["6-alpine", "7-alpine", "7.2-alpine"])
fun `should work with Redis version`(version: String) = runBlocking {
RedisTemplate("test-redis-$version") {
this.version(version)
dynamicPort()
}.use { redis ->
redis.startAndWait()
val pong = redis.redisCommand("PING")
assertEquals("PONG", pong)
val info = redis.redisCommand("INFO", "server")
assertTrue(info.contains("redis_version"))
}
}
}
Testing with Test Containers Pattern¶
abstract class ContainerTest<T : Template> {
protected lateinit var container: T
protected abstract fun createContainer(): T
@BeforeEach
fun setup() {
container = createContainer()
runBlocking { container.startAndWait() }
}
@AfterEach
fun teardown() {
container.close()
}
}
class RedisTest : ContainerTest<RedisTemplate>() {
override fun createContainer() = RedisTemplate("test-redis") {
dynamicPort()
}
@Test
fun `should ping redis`() = runBlocking {
val pong = (container as RedisTemplate).redisCommand("PING")
assertEquals("PONG", pong)
}
}
Parallel Test Execution¶
import org.junit.jupiter.api.parallel.Execution
import org.junit.jupiter.api.parallel.ExecutionMode
@Execution(ExecutionMode.CONCURRENT)
class ParallelContainerTest {
@Test
fun `test 1`() = runBlocking {
// Uses unique container name
RedisTemplate("test-redis-${Thread.currentThread().id}") {
dynamicPort()
}.use { redis ->
redis.startAndWait()
redis.redisCommand("SET", "key1", "value1")
}
}
@Test
fun `test 2`() = runBlocking {
RedisTemplate("test-redis-${Thread.currentThread().id}") {
dynamicPort()
}.use { redis ->
redis.startAndWait()
redis.redisCommand("SET", "key2", "value2")
}
}
}
Spring Boot Integration Test¶
@SpringBootTest
@Testcontainers
class SpringIntegrationTest {
companion object {
private val redis = RedisTemplate("spring-test-redis") {
dynamicPort()
}
@JvmStatic
@BeforeAll
fun setup() = runBlocking {
redis.startAndWait()
}
@JvmStatic
@AfterAll
fun teardown() {
redis.close()
}
@JvmStatic
@DynamicPropertySource
fun properties(registry: DynamicPropertyRegistry) {
registry.add("spring.redis.host") { "localhost" }
registry.add("spring.redis.port") { redis.getMappedPort() }
}
}
@Autowired
lateinit var redisTemplate: StringRedisTemplate
@Test
fun `should use redis`() {
redisTemplate.opsForValue().set("key", "value")
assertEquals("value", redisTemplate.opsForValue().get("key"))
}
}