Skip to content

Multiple Systems (Keyed)

Default: one instance per system type. Need more? Register multiple instances of the same type with typed keys. This is useful for microservice integration, multiple databases, multi-cluster Kafka, and cross-service verification.

In 30 seconds Define keys as Kotlin objects implementing SystemKey. Pass the key as the first argument to any system DSL: postgresql(AppDb) { }, kafka(MainCluster) { }, httpClient(PaymentService) { }. Use the same key inside stove { } to target that instance.

Define keys

Keys are Kotlin singletons implementing SystemKey. They show up in reports and traces, so name them after the role the instance plays.

object AppDb : SystemKey
object AnalyticsDb : SystemKey

object MainCluster : SystemKey
object AuditCluster : SystemKey

object PaymentService : SystemKey
object InventoryService : SystemKey

Why objects, not strings?

Typed keys catch typos at compile time and let IDEs autocomplete usages. Strings would silently bind to "new" instances if misspelled.

Register keyed instances

Pass the key as the first arg. Options and validation APIs match the single-instance API. Default and keyed instances can coexist.

Stove().with {
  // Default, unkeyed Postgres (optional)
  postgresql {
    PostgresqlOptions(/* ... */)
  }

  // Keyed instances: separate containers, ports, and state
  postgresql(AppDb) {
    PostgresqlOptions(
      databaseName = "app",
      configureExposedConfiguration = { cfg ->
        listOf("app.datasource.url=${cfg.jdbcUrl}")
      }
    )
  }

  postgresql(AnalyticsDb) {
    PostgresqlOptions(
      databaseName = "analytics",
      configureExposedConfiguration = { cfg ->
        listOf("analytics.datasource.url=${cfg.jdbcUrl}")
      }
    )
  }

  httpClient(PaymentService) {
    HttpClientSystemOptions(baseUrl = "https://pay.internal")
  }

  httpClient(InventoryService) {
    HttpClientSystemOptions(baseUrl = "https://inv.internal")
  }

  springBoot(runner = { params -> com.app.run(params) })
}.run()

Use keys in tests

Use the same validation DSL and pass the key to target the right instance:

stove {
  postgresql(AppDb) {
    shouldExecute("INSERT INTO users(id) VALUES ('u1')")
  }

  postgresql(AnalyticsDb) {
    shouldQuery<EventRow>("SELECT * FROM events") { it shouldHaveSize 0 }
  }

  httpClient(PaymentService) {
    get<PaymentResponse>("/health") { it.status shouldBe "OK" }
  }

  httpClient(InventoryService) {
    post<InventoryResponse>("/reserve", body) { it.status shouldBe 200 }
  }
}

Supported systems

The built-in systems below support keyed registration:

postgresql · mysql · mssql · mongodb · couchbase · cassandra · redis · elasticsearch · kafka · httpClient · grpc · grpcMock · wiremock

Combine with remote services

For microservice integration tests where your AUT runs locally but dependencies are already deployed, key the remote endpoints:

Stove().with {
  // Your service runs locally
  springBoot(runner = { params -> com.app.run(params) })

  // Upstream / downstream services already running
  httpClient(PaymentService) {
    HttpClientSystemOptions(baseUrl = "https://pay.staging")
  }
  httpClient(InventoryService) {
    HttpClientSystemOptions(baseUrl = "https://inv.staging")
  }
}.run()

Reporting

Keyed instances appear in failure reports with their key name, e.g. kafka[MainCluster] shouldBePublished .... No more guessing which Kafka the assertion targeted.

When NOT to use keys

**One key per logical role.**
object PrimaryDb : SystemKey
object ReadReplica : SystemKey

postgresql(PrimaryDb) { /* ... */ }
postgresql(ReadReplica) { /* ... */ }
**Don't key for sharding or partitioning.** That's an app concern.
object Shard1 : SystemKey
object Shard2 : SystemKey
object Shard3 : SystemKey
// ... 256 more
Tests don't model production sharding; they verify the *behavior* one shard at a time.