Skip to content

Commit 59c83e4

Browse files
committed
added docs on integration testing
1 parent 150112b commit 59c83e4

File tree

2 files changed

+298
-0
lines changed

2 files changed

+298
-0
lines changed

docs/pages/_meta.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export default {
33
"index": "Getting started",
44
"writing-tests" : "Writing tests",
55
"stubbing" : "Stubbing responses",
6+
"integration-testing": "Integration testing",
67
"environment-variables" : "Environment variables",
78
"faq" : "Tips and FAQ's"
89
};

docs/pages/integration-testing.mdx

Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
# Integration Testing with Containers
2+
3+
Preflight provides built-in support for integration testing using [TestContainers](https://testcontainers.com/), allowing you to test against real databases, message brokers, and other infrastructure components.
4+
5+
## Overview
6+
7+
Container-based integration testing in Preflight allows you to:
8+
9+
- Test Taxi schemas against real infrastructure (Kafka, databases, APIs)
10+
- Verify data flows end-to-end with actual message brokers
11+
- Ensure connector configurations work with real systems
12+
- Run isolated, reproducible tests that don't depend on external services
13+
14+
## Getting Started
15+
16+
### 1. Configure Connector Support
17+
18+
First, declare which connectors your tests need in your `build.gradle.kts`:
19+
20+
```kotlin
21+
import com.orbitalhq.preflight.gradle.ConnectorSupport
22+
23+
plugins {
24+
id("com.orbitalhq.preflight")
25+
}
26+
27+
preflight {
28+
connectors = listOf(ConnectorSupport.Kafka)
29+
}
30+
```
31+
32+
This automatically adds the necessary TestContainers dependencies and Orbital connector libraries.
33+
34+
### 2. Define Your Taxi Schema
35+
36+
Create Taxi services that connect to your infrastructure:
37+
38+
```taxi
39+
// src/stock-quotes.taxi
40+
import com.orbitalhq.kafka.KafkaService
41+
import com.orbitalhq.kafka.KafkaOperation
42+
43+
model StockQuote {
44+
ticker : Ticker inherits String
45+
price : Price inherits Decimal
46+
}
47+
48+
@KafkaService( connectionName = "quotes-kafka" )
49+
service StockKafka {
50+
@KafkaOperation( topic = "stockPrices", offset = "earliest" )
51+
stream quotes : Stream<StockQuote>
52+
}
53+
```
54+
55+
### 3. Write Container Tests
56+
57+
Create test specs that use containers:
58+
59+
```kotlin
60+
// test/WithContainersSpec.kt
61+
import app.cash.turbine.test
62+
import com.orbitalhq.expectRawMap
63+
import com.orbitalhq.preflight.dsl.OrbitalSpec
64+
import com.orbitalhq.preflight.dsl.containers.kafka.KafkaContainerSupport
65+
import com.orbitalhq.preflight.dsl.containers.kafka.kafkaContainer
66+
import io.kotest.matchers.shouldBe
67+
68+
class WithContainersSpec : OrbitalSpec({
69+
// Declare containers needed for tests
70+
withContainers(
71+
kafkaContainer("quotes-kafka")
72+
)
73+
74+
describe("integration tests") {
75+
it("should process messages from Kafka") {
76+
val kafkaContainer = containerForConnection<KafkaContainerSupport>("quotes-kafka")
77+
78+
queryForStreamOfObjects("""
79+
stream { StockQuote }
80+
""".trimIndent()).test {
81+
// Send test data to Kafka
82+
kafkaContainer.sendMessage(
83+
"""{ "ticker": "AAPL", "price": 150.25 }""",
84+
"stockPrices"
85+
)
86+
87+
// Verify the data flows through
88+
val result = expectRawMap()
89+
result.shouldBe(mapOf(
90+
"ticker" to "AAPL",
91+
"price" to 150.25
92+
))
93+
}
94+
}
95+
}
96+
})
97+
```
98+
99+
## Kafka Container Support
100+
101+
### Configuration
102+
103+
The Kafka connector provides a pre-configured Kafka container with the necessary setup:
104+
105+
```kotlin
106+
kafkaContainer(
107+
connectionName = "my-kafka", // Must match connectionName in Taxi schema
108+
groupId = "test-consumer" // Optional, auto-generated if not provided
109+
)
110+
```
111+
112+
### Container Interaction API
113+
114+
Once containers are running, access them through the `containerForConnection` method:
115+
116+
```kotlin
117+
val kafkaContainer = containerForConnection<KafkaContainerSupport>("quotes-kafka")
118+
119+
// Send messages to topics
120+
kafkaContainer.sendMessage(
121+
message = """{"field": "value"}""",
122+
topic = "my-topic",
123+
key = "optional-key",
124+
headers = listOf() // Optional headers
125+
)
126+
127+
// Send raw bytes
128+
kafkaContainer.sendMessage(
129+
message = byteArrayOf(1, 2, 3),
130+
topic = "binary-topic"
131+
)
132+
```
133+
134+
### Testing Streaming Queries
135+
136+
Use Turbine's `test` function to verify streaming behavior:
137+
138+
```kotlin
139+
queryForStreamOfObjects("stream { MyModel }").test {
140+
// Send test data
141+
kafkaContainer.sendMessage("""{"field": "value1"}""", "topic")
142+
val first = expectRawMap()
143+
first.shouldBe(mapOf("field" to "value1"))
144+
145+
// Send more data
146+
kafkaContainer.sendMessage("""{"field": "value2"}""", "topic")
147+
val second = expectRawMap()
148+
second.shouldBe(mapOf("field" to "value2"))
149+
150+
// Verify stream completes or continues as expected
151+
}
152+
```
153+
154+
## Container Lifecycle
155+
156+
Containers are managed automatically by Preflight:
157+
158+
1. **Startup**: Containers start before your tests run
159+
2. **Connection**: Orbital connectors are configured with container connection details
160+
3. **Execution**: Your tests interact with real running infrastructure
161+
4. **Cleanup**: Containers are automatically stopped after tests complete
162+
163+
### Lazy Initialization
164+
165+
Container connections are created lazily when you first execute a query. This ensures:
166+
- Containers are fully started and ready
167+
- Connection details (ports, URLs) are available
168+
- Tests don't fail due to timing issues
169+
170+
## Best Practices
171+
172+
### Connection Naming
173+
174+
Use descriptive connection names that match your schema:
175+
176+
```kotlin
177+
// In build.gradle.kts
178+
kafkaContainer("user-events-kafka")
179+
180+
// In schema
181+
@KafkaService( connectionName = "user-events-kafka" )
182+
service UserEventStream { ... }
183+
```
184+
185+
### Test Data Management
186+
187+
Send test data programmatically for reliable tests:
188+
189+
```kotlin
190+
describe("user event processing") {
191+
it("should handle user registration") {
192+
val kafka = containerForConnection<KafkaContainerSupport>("user-events-kafka")
193+
194+
kafka.sendMessage("""
195+
{
196+
"eventType": "USER_REGISTERED",
197+
"userId": "123",
198+
"timestamp": "2024-01-15T10:30:00Z"
199+
}
200+
""".trimIndent(), "user-events")
201+
202+
// Test your query logic...
203+
}
204+
}
205+
```
206+
207+
### Resource Isolation
208+
209+
Each test spec gets fresh containers, ensuring isolation:
210+
211+
```kotlin
212+
class UserEventsSpec : OrbitalSpec({
213+
withContainers(kafkaContainer("events"))
214+
// Tests in this spec share the same container instance
215+
})
216+
217+
class OrderEventsSpec : OrbitalSpec({
218+
withContainers(kafkaContainer("events"))
219+
// This gets a separate container instance
220+
})
221+
```
222+
223+
## Supported Connectors
224+
225+
Currently supported container types:
226+
227+
### Kafka
228+
- **Usage**: `ConnectorSupport.Kafka`
229+
- **Container**: Confluent Platform Kafka
230+
- **Features**: Topic creation, message production, consumer groups
231+
- **API**: `KafkaContainerSupport` with `sendMessage()` methods
232+
233+
### Future Connectors
234+
Additional connectors are planned:
235+
- PostgreSQL databases
236+
- REST APIs with WireMock
237+
- Message queues (RabbitMQ, ActiveMQ)
238+
- Redis caches
239+
240+
## Troubleshooting
241+
242+
### Container Startup Issues
243+
244+
If containers fail to start:
245+
246+
```kotlin
247+
// Increase startup timeout
248+
kafkaContainer("my-kafka").apply {
249+
withStartupTimeout(Duration.ofMinutes(5))
250+
}
251+
```
252+
253+
### Connection Timing
254+
255+
If you get "container not found" errors, ensure:
256+
- Connection names match between `kafkaContainer()` and `@KafkaService(connectionName=...)`
257+
- You call `containerForConnection()` after the query starts (inside test methods)
258+
259+
### Port Conflicts
260+
261+
TestContainers automatically assigns available ports. If you need specific ports:
262+
263+
```kotlin
264+
// Let TestContainers assign ports (recommended)
265+
val kafka = containerForConnection<KafkaContainerSupport>("my-kafka")
266+
// Use kafka.kafkaContainer.bootstrapServers for the actual address
267+
```
268+
269+
## Performance Tips
270+
271+
### Container Reuse
272+
273+
Containers are reused within a spec but not between specs. For faster tests:
274+
275+
```kotlin
276+
// Group related tests in the same spec
277+
class KafkaIntegrationSpec : OrbitalSpec({
278+
withContainers(kafkaContainer("kafka"))
279+
280+
describe("user events") {
281+
it("handles registration") { /* ... */ }
282+
it("handles updates") { /* ... */ }
283+
it("handles deletion") { /* ... */ }
284+
}
285+
})
286+
```
287+
288+
### Parallel Execution
289+
290+
Different specs run in parallel with isolated containers:
291+
292+
```kotlin
293+
// These can run simultaneously
294+
class UserServiceSpec : OrbitalSpec({ /* ... */ })
295+
class OrderServiceSpec : OrbitalSpec({ /* ... */ })
296+
class PaymentServiceSpec : OrbitalSpec({ /* ... */ })
297+
```

0 commit comments

Comments
 (0)