Skip to content
This repository was archived by the owner on Feb 16, 2025. It is now read-only.

Commit a8a3f68

Browse files
committed
Updated JdbcDatabaseContainerExtension
1 parent 0a81dee commit a8a3f68

File tree

6 files changed

+107
-73
lines changed

6 files changed

+107
-73
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Please create issues on the main kotest [board](https://github.com/kotest/kotest
1818

1919
* Deprecated older extensions and introduced `JdbcDatabaseContainerExtension` and `ContainerExtension` extensions
2020
* Deprecated Kafka extensions in favour of the `kotest-extensions-testcontainers-kafka` module
21+
* Added `DockerComposeContainerExtension` for executing test containers from docker compose files.
2122
* Added `kotest-extensions-testcontainers-elastic` and `kotest-extensions-testcontainers-localstack`
2223
* Deprecated per-test lifecycle modes
2324
* Deprecated custom SQL runner in favour of Flyway or another widely used db migration framework.

kotest-extensions-testcontainers-localstack/build.gradle.kts

Lines changed: 0 additions & 6 deletions
This file was deleted.

kotest-extensions-testcontainers-localstack/src/main/kotlin/io/kotest/extensions/testcontainers/localstack/LocalStackContainerExtension.kt

Lines changed: 0 additions & 17 deletions
This file was deleted.

settings.gradle.kts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ pluginManagement {
1010
include(
1111
":kotest-extensions-testcontainers-kafka",
1212
":kotest-extensions-testcontainers-elastic",
13-
":kotest-extensions-testcontainers-localstack",
1413
)
1514

1615
enableFeaturePreview("STABLE_CONFIGURATION_CACHE")

src/main/kotlin/io/kotest/extensions/testcontainers/ContainerExtension.kt

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,6 @@ import org.testcontainers.containers.GenericContainer
2727
* @param mode determines if the container is shutdown after the test suite (project) or after the installed spec
2828
* The default is after the test suite.
2929
*
30-
* @param beforeSpec a beforeSpec callback
31-
* @param afterSpec an afterSpec callback
32-
* @param beforeTest a beforeTest callback
33-
* @param afterTest a afterTest callback
34-
*
3530
* @param beforeStart a callback that is invoked only once, just before the container is started.
3631
* If the container is never started, this callback will not be invoked.
3732
* This callback can be useful instead of the installation callback as it will only
@@ -40,6 +35,18 @@ import org.testcontainers.containers.GenericContainer
4035
* @param afterStart a callback that is invoked only once, just after the container is started.
4136
* If the container is never started, this callback will not be invoked.
4237
*
38+
* @param beforeSpec a beforeSpec callback.
39+
* If the container is never started, this callback will not be invoked.
40+
*
41+
* @param afterSpec an afterSpec callback
42+
* If the container is never started, this callback will not be invoked.
43+
*
44+
* @param beforeTest a beforeTest callback
45+
* If the container is never started, this callback will not be invoked.
46+
*
47+
* @param afterTest a afterTest callback
48+
* If the container is never started, this callback will not be invoked.
49+
*
4350
* @param beforeShutdown a callback that is invoked only once, just before the container is stopped.
4451
* If the container is never started, this callback will not be invoked.
4552
*
@@ -49,14 +56,14 @@ import org.testcontainers.containers.GenericContainer
4956
class ContainerExtension<T : GenericContainer<T>>(
5057
private val container: T,
5158
private val mode: ContainerLifecycleMode = ContainerLifecycleMode.Project,
52-
private val beforeStart: (T) -> Unit = {},
53-
private val afterStart: (T) -> Unit = {},
54-
private val beforeTest: suspend (TestCase, T) -> Unit = { _, _ -> },
55-
private val afterTest: suspend (TestCase, T) -> Unit = { _, _ -> },
56-
private val beforeSpec: suspend (Spec, T) -> Unit = { _, _ -> },
57-
private val afterSpec: suspend (Spec, T) -> Unit = { _, _ -> },
58-
private val beforeShutdown: (T) -> Unit = {},
59-
private val afterShutdown: (T) -> Unit = {},
59+
private val beforeStart: () -> Unit = {},
60+
private val afterStart: () -> Unit = {},
61+
private val beforeTest: suspend (TestCase) -> Unit = { _ -> },
62+
private val afterTest: suspend (TestCase) -> Unit = { _ -> },
63+
private val beforeSpec: suspend (Spec) -> Unit = { _ -> },
64+
private val afterSpec: suspend (Spec) -> Unit = { _ -> },
65+
private val beforeShutdown: () -> Unit = {},
66+
private val afterShutdown: () -> Unit = {},
6067
) : MountableExtension<T, T>,
6168
AfterProjectListener,
6269
BeforeTestListener,
@@ -70,28 +77,28 @@ class ContainerExtension<T : GenericContainer<T>>(
7077
*/
7178
override fun mount(configure: T.() -> Unit): T {
7279
if (!container.isRunning) {
73-
beforeStart(container)
80+
beforeStart()
7481
container.start()
75-
afterStart(container)
82+
afterStart()
7683
}
7784
container.configure()
7885
return container
7986
}
8087

8188
override suspend fun beforeTest(testCase: TestCase) {
82-
beforeTest(testCase, container)
89+
beforeTest(testCase)
8390
}
8491

8592
override suspend fun afterTest(testCase: TestCase, result: TestResult) {
86-
afterTest(testCase, container)
93+
afterTest(testCase)
8794
}
8895

8996
override suspend fun beforeSpec(spec: Spec) {
90-
beforeSpec(spec, container)
97+
beforeSpec(spec)
9198
}
9299

93100
override suspend fun afterSpec(spec: Spec) {
94-
afterSpec(spec, container)
101+
afterSpec(spec)
95102
if (mode == ContainerLifecycleMode.Spec && container.isRunning) close()
96103
}
97104

@@ -101,9 +108,9 @@ class ContainerExtension<T : GenericContainer<T>>(
101108

102109
private suspend fun close() {
103110
withContext(Dispatchers.IO) {
104-
beforeShutdown(container)
111+
beforeShutdown()
105112
container.stop()
106-
afterShutdown(container)
113+
afterShutdown()
107114
}
108115
}
109116
}
Lines changed: 78 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,40 @@
11
package io.kotest.extensions.testcontainers
22

3+
import com.zaxxer.hikari.HikariConfig
34
import com.zaxxer.hikari.HikariDataSource
45
import io.kotest.core.extensions.MountableExtension
56
import io.kotest.core.listeners.AfterProjectListener
67
import io.kotest.core.listeners.AfterSpecListener
78
import io.kotest.core.listeners.AfterTestListener
89
import io.kotest.core.listeners.BeforeSpecListener
910
import io.kotest.core.listeners.BeforeTestListener
11+
import io.kotest.core.spec.Spec
12+
import io.kotest.core.test.TestCase
13+
import io.kotest.core.test.TestResult
1014
import kotlinx.coroutines.Dispatchers
1115
import kotlinx.coroutines.withContext
1216
import org.testcontainers.containers.JdbcDatabaseContainer
1317

1418
/**
15-
* A Kotest [MountableExtension] for [JdbcDatabaseContainer]s that are started the first time they are
16-
* installed in a spec.
19+
* A Kotest [MountableExtension] for [JdbcDatabaseContainer]s which is started the first time they are
20+
* installed in a spec. Upon installation, this extension returns an initialized [HikariDataSource]
21+
* connected to the database.
1722
*
1823
* If no spec is executed that installs a particular container,
1924
* then that container is never started.
2025
*
21-
* Once mounted in a spec, the return value from the installation point is a configured HikariDataSource.
22-
*
2326
* @param container the specific database test container type
2427
* @param beforeSpec a beforeSpec callback
28+
* If the container is never started, this callback will not be invoked.
29+
*
2530
* @param afterSpec an afterSpec callback
31+
* If the container is never started, this callback will not be invoked.
32+
*
2633
* @param beforeTest a beforeTest callback
34+
* If the container is never started, this callback will not be invoked.
35+
*
2736
* @param afterTest a afterTest callback
37+
* If the container is never started, this callback will not be invoked.
2838
*
2939
* @param beforeStart a callback that is invoked only once, just before the container is started.
3040
* If the container is never started, this callback will not be invoked.
@@ -42,48 +52,88 @@ import org.testcontainers.containers.JdbcDatabaseContainer
4252
*/
4353
class JdbcDatabaseContainerExtension(
4454
private val container: JdbcDatabaseContainer<*>,
45-
private val beforeStart: (JdbcDatabaseContainer<*>) -> Unit = {},
46-
private val afterStart: (JdbcDatabaseContainer<*>) -> Unit = {},
47-
private val beforeShutdown: (JdbcDatabaseContainer<*>) -> Unit = {},
48-
private val beforeTest: suspend (HikariDataSource) -> Unit = {},
49-
private val afterTest: suspend (HikariDataSource) -> Unit = {},
50-
private val beforeSpec: suspend (HikariDataSource) -> Unit = {},
51-
private val afterSpec: suspend (HikariDataSource) -> Unit = {},
52-
private val afterShutdown: (HikariDataSource) -> Unit = {},
53-
) : MountableExtension<TestContainerHikariConfig, HikariDataSource>,
55+
private val mode: ContainerLifecycleMode = ContainerLifecycleMode.Project,
56+
private val beforeStart: () -> Unit = {},
57+
private val afterStart: () -> Unit = {},
58+
private val beforeTest: suspend (TestCase, HikariDataSource) -> Unit = { _, _ -> },
59+
private val afterTest: suspend (TestCase, HikariDataSource) -> Unit = { _, _ -> },
60+
private val beforeSpec: suspend (Spec) -> Unit = { _ -> },
61+
private val afterSpec: suspend (Spec, HikariDataSource) -> Unit = { _, _ -> },
62+
private val beforeShutdown: (HikariDataSource) -> Unit = {},
63+
private val afterShutdown: () -> Unit = { },
64+
) : MountableExtension<HikariDataSource, HikariDataSource>,
5465
AfterProjectListener,
5566
BeforeTestListener,
5667
BeforeSpecListener,
5768
AfterTestListener,
5869
AfterSpecListener {
5970

71+
private var dataSource: HikariDataSource? = null
72+
6073
/**
6174
* Mounts the container, starting it if necessary. The [configure] block will be invoked
6275
* every time the container is mounted, and after the container has started.
76+
*
77+
* This will return an initialized [HikariDataSource].
78+
* The datasource will be closed in accordance with the provided [ContainerLifecycleMode].
79+
*
80+
* The datasource will be created with default options. If you wish to customize the datasource,
81+
* then ignore this returned value, and instead use the extension function [toDataSource] on the
82+
* container instance.
6383
*/
64-
override fun mount(configure: TestContainerHikariConfig.() -> Unit): HikariDataSource {
84+
override fun mount(configure: HikariDataSource.() -> Unit): HikariDataSource {
6585
if (!container.isRunning) {
66-
beforeStart(container)
86+
beforeStart()
6787
container.start()
68-
afterStart(container)
88+
dataSource = container.toDataSource()
89+
afterStart()
6990
}
70-
return createDataSource(configure)
91+
dataSource?.configure()
92+
return dataSource ?: error("Datasource not initialized")
93+
}
94+
95+
override suspend fun beforeTest(testCase: TestCase) {
96+
beforeTest(testCase, dataSource ?: error("Datasource not initialized"))
97+
}
98+
99+
override suspend fun afterTest(testCase: TestCase, result: TestResult) {
100+
afterTest(testCase, dataSource ?: error("Datasource not initialized"))
101+
}
102+
103+
override suspend fun beforeSpec(spec: Spec) {
104+
beforeSpec(spec)
105+
}
106+
107+
override suspend fun afterSpec(spec: Spec) {
108+
afterSpec(spec, dataSource ?: error("Datasource not initialized"))
109+
if (mode == ContainerLifecycleMode.Spec && container.isRunning) close()
71110
}
72111

73112
override suspend fun afterProject() {
74-
if (container.isRunning) withContext(Dispatchers.IO) {
75-
beforeShutdown(container)
113+
if (container.isRunning) close()
114+
}
115+
116+
private suspend fun close() {
117+
withContext(Dispatchers.IO) {
118+
beforeShutdown(dataSource ?: error("Datasource not initialized"))
119+
dataSource?.close()
76120
container.stop()
121+
afterShutdown()
77122
}
78123
}
124+
}
79125

80-
private fun createDataSource(configure: TestContainerHikariConfig.() -> Unit): HikariDataSource {
81-
val config = TestContainerHikariConfig()
82-
config.jdbcUrl = container.jdbcUrl
83-
config.username = container.username
84-
config.password = container.password
85-
config.configure()
86-
// runInitScripts(ds.connection, config.dbInitScripts)
87-
return HikariDataSource(config)
88-
}
126+
/**
127+
* Returns an initialized [HikariDataSource] connected to this [JdbcDatabaseContainer].
128+
*
129+
* @param configure a thunk to configure the [HikariConfig] used to create the datasource.
130+
*/
131+
fun JdbcDatabaseContainer<*>.toDataSource(configure: HikariConfig.() -> Unit = {}): HikariDataSource {
132+
val config = HikariConfig()
133+
config.jdbcUrl = jdbcUrl
134+
config.username = username
135+
config.password = password
136+
config.minimumIdle = 0
137+
config.configure()
138+
return HikariDataSource(config)
89139
}

0 commit comments

Comments
 (0)