Provided Instances¶
Connect Stove to existing infrastructure such as shared CI databases, dev clusters, or staging brokers instead of spinning Testcontainers. The registered system DSL and assertions stay the same; infrastructure lifecycle and cleanup become your responsibility.
In 30 seconds
Each listed dependency system options class ships a .provided(...) factory. Swap SystemOptions(...) for SystemOptions.provided(...), supply connection details, and Stove connects to your infrastructure instead of starting a container. For shared infra, prefix every resource (schemas, topics, indexes, keys) with a unique run ID to prevent collisions.
The pattern¶
Container mode (default)
The registration shape stays identical, but the operational contract changes: Stove does not create, isolate, pause, or destroy a provided dependency.
Supported systems¶
The built-in dependency systems below support provided instances. Signatures are similar; check the specific reference page for full details and cleanup support.
| System | Factory | Required args |
|---|---|---|
| PostgreSQL | PostgresqlOptions.provided |
jdbcUrl, username, password |
| MySQL | MySqlOptions.provided |
jdbcUrl, username, password |
| MSSQL | MsSqlOptions.provided |
jdbcUrl, username, password |
| MongoDB | MongodbSystemOptions.provided |
connectionString |
| Couchbase | CouchbaseSystemOptions.provided |
bucketName, hostsWithPort, username, password |
| Cassandra | CassandraSystemOptions.provided |
contactPoints, keyspace |
| Redis | RedisSystemOptions.provided |
url |
| Elasticsearch | ElasticsearchSystemOptions.provided |
url |
| Kafka | KafkaSystemOptions.provided |
bootstrapServers |
Each accepts the same configureExposedConfiguration and (where applicable) cleanup lambdas as the container mode.
Cleanup¶
Container mode isolates most state by removing containers when Stove stops. Provided instances persist across runs, so you must clean test data yourself. Every provided options class accepts a cleanup lambda that runs on suite teardown.
postgresql {
PostgresqlOptions.provided(
jdbcUrl = TestRunContext.jdbcUrl,
cleanup = { ops ->
ops.execute("DROP SCHEMA IF EXISTS ${TestRunContext.schemaName} CASCADE")
},
configureExposedConfiguration = { cfg -> listOf(/* ... */) }
)
}
kafka {
KafkaSystemOptions.provided(
bootstrapServers = "shared-kafka:9092",
cleanup = { admin ->
val testTopics = admin.listTopics().names().get()
.filter { it.startsWith(TestRunContext.topicPrefix) }
if (testTopics.isNotEmpty()) {
admin.deleteTopics(testTopics).all().get()
}
},
configureExposedConfiguration = { cfg -> listOf(/* ... */) }
)
}
Migrations¶
By default, migrations run only in container mode (you don't want test migrations smashing shared schemas). Opt-in for provided:
postgresql {
PostgresqlOptions.provided(
jdbcUrl = "...",
runMigrations = true, // default: false for provided
/* ... */
).migrations { register<CreateTablesMigration>() }
}
Apply with care on shared infra.
Limitations¶
| Feature | Container | Provided |
|---|---|---|
pause() / unpause() |
✓ | ✗ |
inspect() for container internals |
✓ | ✗ |
| Automatic cleanup | ✓ | manual via cleanup lambda |
keepDependenciesRunning |
yes (container survives) | n/a (you didn't start it) |
Shared infrastructure: isolation pattern¶
Parallel CI runs against the same Postgres/Kafka collide without isolation. Solution: unique resource prefix per run.
object TestRunContext {
val runId: String = System.getenv("CI_JOB_ID")
?: UUID.randomUUID().toString().take(8)
val schemaName = "test_$runId"
val topicPrefix = "test_${runId}_"
val indexPrefix = "test_${runId}_"
val keyPrefix = "test:${runId}:"
}
Wire it everywhere:
postgresql {
PostgresqlOptions.provided(
jdbcUrl = "jdbc:postgresql://shared-db:5432/test?currentSchema=${TestRunContext.schemaName}",
cleanup = { ops ->
ops.execute("DROP SCHEMA IF EXISTS ${TestRunContext.schemaName} CASCADE")
},
configureExposedConfiguration = { cfg -> listOf(
"spring.datasource.url=${cfg.jdbcUrl}"
) }
)
}
springBoot(withParameters = listOf(
"app.kafka.topic.orders=${TestRunContext.topicPrefix}orders",
"app.kafka.topic.audit=${TestRunContext.topicPrefix}audit"
))
kafka {
KafkaSystemOptions.provided(
bootstrapServers = "shared-kafka:9092",
cleanup = { admin ->
val topics = admin.listTopics().names().get()
.filter { it.startsWith(TestRunContext.topicPrefix) }
if (topics.isNotEmpty()) admin.deleteTopics(topics).all().get()
},
configureExposedConfiguration = { cfg -> listOf(/* ... */) }
)
}
springBoot(withParameters = listOf(
"app.es.index.products=${TestRunContext.indexPrefix}products"
))
elasticsearch {
ElasticsearchSystemOptions.provided(
url = "http://shared-es:9200",
cleanup = { client ->
client.indices().delete { it.index("${TestRunContext.indexPrefix}*") }
},
configureExposedConfiguration = { cfg -> listOf(/* ... */) }
)
}
springBoot(withParameters = listOf(
"app.cache.prefix=${TestRunContext.keyPrefix}"
))
redis {
RedisSystemOptions.provided(
url = "redis://shared-redis:6379",
cleanup = { client ->
val keys = client.keys("${TestRunContext.keyPrefix}*")
if (keys.isNotEmpty()) client.del(*keys.toTypedArray())
},
configureExposedConfiguration = { cfg -> listOf(/* ... */) }
)
}
Same idea: prefix collection / bucket / table names, drop in cleanup. See each system's reference for the exact API.
Isolation cheat sheet¶
| Symptom | Likely cause | Fix |
|---|---|---|
| Parallel runs read each other's rows | Same schema/topic/index name | Add runId to resource prefix |
| Cleanup leaves orphans | Cleanup ran but missed a prefix | Log TestRunContext.runId at suite start; reconcile during nightly job |
| Run hangs forever | App writing to prefixed topics that don't exist | Enable broker-level auto-create or pre-create topics in setup |
Complete CI/CD example¶
class CIE2EConfig : AbstractProjectConfig() {
override suspend fun beforeProject() {
Stove().with {
httpClient {
HttpClientSystemOptions(baseUrl = "http://localhost:8080")
}
postgresql {
PostgresqlOptions.provided(
jdbcUrl = "jdbc:postgresql://shared-db:5432/test?currentSchema=${TestRunContext.schemaName}",
username = System.getenv("PG_USER"),
password = System.getenv("PG_PASS"),
cleanup = { ops ->
ops.execute("DROP SCHEMA IF EXISTS ${TestRunContext.schemaName} CASCADE")
},
configureExposedConfiguration = { cfg -> listOf(
"spring.datasource.url=${cfg.jdbcUrl}",
"spring.datasource.username=${cfg.username}",
"spring.datasource.password=${cfg.password}"
) }
)
}
kafka {
KafkaSystemOptions.provided(
bootstrapServers = System.getenv("KAFKA_BOOTSTRAP"),
cleanup = { admin ->
admin.listTopics().names().get()
.filter { it.startsWith(TestRunContext.topicPrefix) }
.takeIf { it.isNotEmpty() }
?.let { admin.deleteTopics(it).all().get() }
},
configureExposedConfiguration = { cfg -> listOf(
"spring.kafka.bootstrap-servers=${cfg.bootstrapServers}"
) }
)
}
springBoot(
runner = { params -> com.app.run(params) },
withParameters = listOf(
"app.kafka.topic.orders=${TestRunContext.topicPrefix}orders"
)
)
}.run()
}
override suspend fun afterProject() = Stove.stop()
}
Best practices¶
Always log
TestRunContext.runIdat suite start for forensic debuggingReconcile orphans nightly (cleanup hooks can fail mid-run)
Use stable
CI_JOB_IDover random when available. Easier to traceDon't share a single schema/topic across runs
Don't skip cleanup hooks "for speed"; shared-state debt compounds
Related¶
- Best Practices · shared infra
- Provided Application for black-box smoke testing
- Per-system reference: PostgreSQL, Kafka, MongoDB, Redis, ...