Bridge¶
Reach into your app's DI container from a test. Read internal state and drive domain services. Test-only bean registration and replacement depend on the framework; Quarkus Bridge provides CDI lookup but does not replace beans.
When to use Bridge
You need state or behavior that isn't exposed through HTTP/Kafka/DB surfaces. Verifying internal flags, exercising a domain method directly, swapping Clock for deterministic time, capturing emitted domain events. If a public surface tells you the answer, prefer that instead.
Quarkus setup
Import com.trendyol.stove.quarkus.bridge. Quarkus requires an augmented uber-jar on the shared test classpath and unused-bean removal disabled in the e2e build. See the Quarkus Bridge guide. The isolated fast-jar launcher is not supported by Bridge.
Setup¶
Built into the framework starters. No extra dependency.
Ktor auto-detects Koin vs Ktor-DI vs custom (see Ktor guide).
The using<T> { } DSL¶
Inside stove { }, request beans from the AUT:
Multi-bean fetch. Up to 5 generic args:
stove {
using<OrderService, PaymentService> { orderSvc, paySvc ->
orderSvc.place(order)
paySvc.charge(order.amount)
}
}
The lambda receives beans in declaration order. Generics resolve correctly (using<List<Validator>> { ... } returns the bound list).
Capture values out of the lambda¶
stove {
val savedOrder = using<OrderRepository> {
findById(orderId).orElseThrow()
}
savedOrder.status shouldBe "CREATED"
}
using<T> returns whatever the lambda returns.
Register test-only beans¶
Inject test doubles or interceptors at runner setup time. Pattern depends on framework + version.
Real use cases¶
Seed via the app's own repository so all invariants apply.
Inspect cache contents, in-memory counters, processed-message lists.
Call a service directly when there's no HTTP path (scheduled jobs, listeners).
Inject Clock / TimeProvider for deterministic tests.
Register an @EventListener to assert on domain events.
Manually advance a scheduled job, drain a retry queue, etc.
Time-controlled example¶
class FixedClock(private val instant: Instant) : Clock() {
override fun getZone() = ZoneOffset.UTC
override fun withZone(zone: ZoneId) = this
override fun instant() = instant
}
// Test
test("order expires after 24h") {
stove {
val clock = using<Clock> { this }
http { post("/orders", body) { it.status shouldBe 201 } }
(clock as FixedClock).advance(Duration.ofHours(25))
using<OrderService> {
getOrder(orderId).status shouldBe "EXPIRED"
}
}
}
Domain event capture¶
@Component
class TestEventCaptor {
val captured = ConcurrentLinkedQueue<DomainEvent>()
@EventListener fun on(e: DomainEvent) { captured.add(e) }
}
// Register in test dependencies, then assert:
stove {
http { post("/orders", body) { it.status shouldBe 201 } }
using<TestEventCaptor> {
eventually(10.seconds) {
captured.any { it is OrderCreated && it.id == orderId }
}
}
}
When NOT to use Bridge¶
Troubleshooting¶
| Symptom | Check |
|---|---|
bridge() not available |
Import the selected framework's extension, such as com.trendyol.stove.quarkus.bridge |
| Quarkus: ArC inaccessible or fast-jar rejected | Use the augmented uber-jar classpath and preserve beans in the e2e build |
NoSuchBeanDefinitionException |
Bean exists in production code? Type matches the bound interface? |
| Ktor: wrong DI container detected | Pass explicit resolver: bridge { app, type -> myContainer.resolve(type) } |
| Test bean not overriding production | Spring 2.x/3.x: isPrimary = true. Spring 4.x: primary = true |
| Generics not resolving | Use using<List<T>> { } form; Stove resolves via Kotlin reified types |