Skip to content

Commit

Permalink
Added ContainerExtension and deprecated older extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
sksamuel committed Jun 18, 2023
1 parent 0f6a1ab commit a9aa6fd
Show file tree
Hide file tree
Showing 13 changed files with 90 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,20 @@ import co.elastic.clients.elasticsearch.ElasticsearchClient
import co.elastic.clients.json.jackson.JacksonJsonpMapper
import co.elastic.clients.transport.rest_client.RestClientTransport
import io.kotest.extensions.testcontainers.AbstractContainerExtension
import io.kotest.extensions.testcontainers.TestContainerLifecycleMode
import io.kotest.extensions.testcontainers.ContainerLifecycleMode
import org.apache.http.HttpHost
import org.elasticsearch.client.RestClient
import org.testcontainers.elasticsearch.ElasticsearchContainer
import org.testcontainers.utility.DockerImageName

class ElasticsearchContainerExtension(
container: ElasticsearchContainer,
mode: TestContainerLifecycleMode = TestContainerLifecycleMode.Project,
mode: ContainerLifecycleMode = ContainerLifecycleMode.Project,
) : AbstractContainerExtension<ElasticsearchContainer>(container, mode) {

constructor(
image: DockerImageName,
mode: TestContainerLifecycleMode = TestContainerLifecycleMode.Project
mode: ContainerLifecycleMode = ContainerLifecycleMode.Project
) : this(ElasticsearchContainer(image), mode)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package io.kotest.extensions.testcontainers.kafka

import io.kotest.extensions.testcontainers.AbstractContainerExtension
import io.kotest.extensions.testcontainers.TestContainerLifecycleMode
import io.kotest.extensions.testcontainers.ContainerLifecycleMode
import org.apache.kafka.clients.CommonClientConfigs
import org.apache.kafka.clients.consumer.ConsumerConfig
import org.apache.kafka.clients.consumer.KafkaConsumer
Expand All @@ -16,12 +16,12 @@ import java.util.Properties

class KafkaContainerExtension(
container: KafkaContainer,
mode: TestContainerLifecycleMode = TestContainerLifecycleMode.Project,
mode: ContainerLifecycleMode = ContainerLifecycleMode.Project,
) : AbstractContainerExtension<KafkaContainer>(container, mode) {

constructor(
image: DockerImageName,
mode: TestContainerLifecycleMode = TestContainerLifecycleMode.Project
mode: ContainerLifecycleMode = ContainerLifecycleMode.Project
) : this(KafkaContainer(image), mode)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
package io.kotest.extensions.testcontainers.localstack

import io.kotest.extensions.testcontainers.AbstractContainerExtension
import io.kotest.extensions.testcontainers.TestContainerLifecycleMode
import io.kotest.extensions.testcontainers.ContainerLifecycleMode
import org.testcontainers.containers.localstack.LocalStackContainer
import org.testcontainers.utility.DockerImageName

class LocalStackContainerExtension(
container: LocalStackContainer,
mode: TestContainerLifecycleMode = TestContainerLifecycleMode.Project,
mode: ContainerLifecycleMode = ContainerLifecycleMode.Project,
) : AbstractContainerExtension<LocalStackContainer>(container, mode) {

constructor(
image: DockerImageName,
mode: TestContainerLifecycleMode = TestContainerLifecycleMode.Project
mode: ContainerLifecycleMode = ContainerLifecycleMode.Project
) : this(LocalStackContainer(image), mode)
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,17 @@ import org.testcontainers.containers.GenericContainer

abstract class AbstractContainerExtension<T : GenericContainer<T>>(
private val container: T,
private val mode: TestContainerLifecycleMode = TestContainerLifecycleMode.Project,
private val mode: ContainerLifecycleMode = ContainerLifecycleMode.Project,
) : AfterProjectListener,
AfterSpecListener,
MountableExtension<T, T>,
AutoCloseable {

override fun mount(configure: T.() -> Unit): T {
container.start()
container.configure()
if (!container.isRunning) {
container.configure()
container.start()
}
return container
}

Expand All @@ -25,7 +27,7 @@ abstract class AbstractContainerExtension<T : GenericContainer<T>>(
}

final override suspend fun afterSpec(spec: Spec) {
if (mode == TestContainerLifecycleMode.Spec && container.isRunning) close()
if (mode == ContainerLifecycleMode.Spec && container.isRunning) close()
}

final override fun close() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,67 @@ package io.kotest.extensions.testcontainers

import io.kotest.core.extensions.MountableExtension
import io.kotest.core.listeners.AfterProjectListener
import io.kotest.core.listeners.AfterSpecListener
import io.kotest.core.listeners.AfterTestListener
import io.kotest.core.listeners.BeforeSpecListener
import io.kotest.core.listeners.BeforeTestListener
import io.kotest.core.spec.Spec
import io.kotest.core.test.TestCase
import io.kotest.core.test.TestResult
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.testcontainers.containers.GenericContainer

/**
* A Kotest [MountableExtension] for [GenericContainer]s that are started the first time they are
* installed in a spec, and then shared throughout the same test suite. The container is shutdown
* after the test suite has completed.
* installed in a spec.
*
* If no spec is executed that installs a particular container,
* then that container is never started.
*
* Containers can be shared between specs using the default [mode].
*
* @param container the test container instance
*
* @param mode determines if the container is shutdown after the test suite (project) or after the installed spec
* The default is after the test suite.
*
* @param beforeSpec a beforeSpec callback
* @param afterSpec an afterSpec callback
* @param beforeTest a beforeTest callback
* @param afterTest a afterTest callback
*
* @param beforeStart a callback that is invoked only once, just before the container is started.
* If the container is never started, this callback will not be invoked.
* If the container is never started, this callback will not be invoked.
* This callback can be useful instead of the installation callback as it will only
* be executed once, regardless of how many times this container is installed.
*
* @param afterStart a callback that is invoked only once, just after the container is started.
* If the container is never started, this callback will not be invoked.
* @param beforeShutdown a callback that is invoked only once, just before the containuer is stopped.
* If the container is never started, this callback will not be invoked.
* If the container is never started, this callback will not be invoked.
*
* @param beforeShutdown a callback that is invoked only once, just before the container is stopped.
* If the container is never started, this callback will not be invoked.
*
* @param afterShutdown a callback that is invoked only once, just after the container is stopped.
* If the container is never started, this callback will not be invoked.
*/
class ContainerExtension<T : GenericContainer<*>>(
class ContainerExtension<T : GenericContainer<T>>(
private val container: T,
private val mode: ContainerLifecycleMode = ContainerLifecycleMode.Project,
private val beforeTest: suspend (TestCase, T) -> Unit = { _, _ -> },
private val afterTest: suspend (TestCase, T) -> Unit = { _, _ -> },
private val beforeSpec: suspend (Spec, T) -> Unit = { _, _ -> },
private val afterSpec: suspend (Spec, T) -> Unit = { _, _ -> },
private val beforeStart: (T) -> Unit = {},
private val afterStart: (T) -> Unit = {},
private val beforeShutdown: (T) -> Unit = {},
private val afterShutdown: (T) -> Unit = {},
) : MountableExtension<T, T>,
AfterProjectListener {
AfterProjectListener,
BeforeTestListener,
BeforeSpecListener,
AfterTestListener,
AfterSpecListener {

/**
* Mounts the container, starting it if necessary. The [configure] block will be invoked
Expand All @@ -43,10 +78,32 @@ class ContainerExtension<T : GenericContainer<*>>(
return container
}

override suspend fun beforeTest(testCase: TestCase) {
beforeTest(testCase, container)
}

override suspend fun afterTest(testCase: TestCase, result: TestResult) {
afterTest(testCase, container)
}

override suspend fun beforeSpec(spec: Spec) {
beforeSpec(spec, container)
}

override suspend fun afterSpec(spec: Spec) {
afterSpec(spec, container)
if (mode == ContainerLifecycleMode.Spec && container.isRunning) close()
}

override suspend fun afterProject() {
if (container.isRunning) withContext(Dispatchers.IO) {
if (container.isRunning) close()
}

private suspend fun close() {
withContext(Dispatchers.IO) {
beforeShutdown(container)
container.stop()
afterShutdown(container)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import org.testcontainers.lifecycle.TestLifecycleAware
import java.util.Optional
import org.testcontainers.containers.DockerComposeContainer

@Deprecated("use DockerComposeContainerExtension")
class DockerComposeContainersExtension<T : DockerComposeContainer<out T>>(
private val container: DockerComposeContainer<out T>,
private val lifecycleMode: LifecycleMode = LifecycleMode.Spec,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ enum class LifecycleMode {
/**
* Determines the lifetime of a test container installed in a Kotest extension.
*/
enum class TestContainerLifecycleMode {
enum class ContainerLifecycleMode {

/**
* The TestContainer is started only when first installed and stopped after the spec where it was
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import org.testcontainers.containers.GenericContainer
*
* @since 1.3.0
*/
@Deprecated("use ContainerExtension")
class SharedTestContainerExtension<T : GenericContainer<*>, U>(
private val container: T,
private val beforeTest: suspend (T) -> Unit = {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import org.testcontainers.lifecycle.Startable
* @see
* [StartablePerTestListener]
* */

@Deprecated("use ContainerExtension")
class StartablePerProjectListener<T : Startable>(val startable: T, val containerName: String) : TestListener, ProjectListener {
override val name = containerName
private val testLifecycleAwareListener = TestLifecycleAwareListener(startable)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import org.testcontainers.lifecycle.TestLifecycleAware
* @see
* [StartablePerTestListener]
* */

@Deprecated("use ContainerExtension")
class StartablePerSpecListener<T : Startable>(val startable: T) : TestListener {
private val testLifecycleAwareListener = TestLifecycleAwareListener(startable)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import org.testcontainers.lifecycle.TestLifecycleAware
*
* @see[StartablePerSpecListener]
* */
@Deprecated("use ContainerExtension")
class StartablePerTestListener<T : Startable>(val startable: T) : TestListener {
private val testLifecycleAwareListener = TestLifecycleAwareListener(startable)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import org.testcontainers.containers.GenericContainer
import org.testcontainers.lifecycle.TestLifecycleAware
import java.util.Optional

@Deprecated("use ContainerExtension")
class TestContainerExtension<T : GenericContainer<out T>>(
private val container: GenericContainer<out T>,
private val lifecycleMode: LifecycleMode = LifecycleMode.Spec,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import org.testcontainers.lifecycle.TestLifecycleAware
import java.net.URLEncoder
import java.util.Optional

@Deprecated("use ContainerExtension")
class TestLifecycleAwareListener(startable: Startable) : TestListener {
private val testLifecycleAware = startable as? TestLifecycleAware

Expand All @@ -21,6 +22,7 @@ class TestLifecycleAwareListener(startable: Startable) : TestListener {
}
}

@Deprecated("use ContainerExtension")
internal fun TestCase.toTestDescription() = object : TestDescription {

override fun getFilesystemFriendlyName(): String {
Expand Down

0 comments on commit a9aa6fd

Please sign in to comment.