|
| 1 | +# Release Notes: 0.19.0 |
| 2 | + |
| 3 | +**Release Date:** December 2025 |
| 4 | + |
| 5 | +This release introduces: |
| 6 | + |
| 7 | +- **gRPC System**: New component for testing gRPC APIs (grpc-kotlin, Wire) |
| 8 | +- **WebSocket Testing**: Added to HTTP system for real-time communication testing |
| 9 | +- **Partial Mocking**: WireMock now supports `mockPostContaining`, `mockPutContaining`, `mockPatchContaining` |
| 10 | +- **Embedded Kafka**: Run Kafka tests without Docker using `useEmbeddedKafka = true` |
| 11 | +- **Provided Instances**: PostgreSQL, MSSQL, MongoDB, Couchbase, Elasticsearch, Redis, and Kafka support connecting to external infrastructure |
| 12 | +- **Pause/Unpause**: PostgreSQL, MSSQL, MongoDB, Couchbase, Elasticsearch, Redis, and Kafka support container pause/unpause for resilience testing |
| 13 | +- **Response Headers**: WireMock mocks now support custom response headers |
| 14 | + |
| 15 | +--- |
| 16 | + |
| 17 | +## New Features |
| 18 | + |
| 19 | +### gRPC Support |
| 20 | + |
| 21 | +Stove now supports testing gRPC APIs with a fluent DSL. The new `grpc` system works with multiple gRPC providers including grpc-kotlin, Wire, and standard gRPC stubs. |
| 22 | + |
| 23 | +```kotlin |
| 24 | +// Using typed channels (grpc-kotlin, Wire stubs) |
| 25 | +grpc { |
| 26 | + channel<GreeterServiceStub> { |
| 27 | + val response = sayHello(HelloRequest(name = "World")) |
| 28 | + response.message shouldBe "Hello, World!" |
| 29 | + } |
| 30 | +} |
| 31 | + |
| 32 | +// Using Wire clients |
| 33 | +grpc { |
| 34 | + wireClient<GreeterServiceClient> { |
| 35 | + val response = SayHello().execute(HelloRequest(name = "World")) |
| 36 | + response.message shouldBe "Hello, World!" |
| 37 | + } |
| 38 | +} |
| 39 | +``` |
| 40 | + |
| 41 | +All streaming types work naturally with Kotlin coroutines: |
| 42 | + |
| 43 | +```kotlin |
| 44 | +grpc { |
| 45 | + channel<StreamServiceStub> { |
| 46 | + // Server streaming |
| 47 | + serverStream(request).collect { response -> |
| 48 | + // assertions on each response |
| 49 | + } |
| 50 | + |
| 51 | + // Client streaming |
| 52 | + val response = clientStream(flow { emit(request1); emit(request2) }) |
| 53 | + |
| 54 | + // Bidirectional streaming |
| 55 | + bidiStream(requestFlow).collect { response -> |
| 56 | + // assertions |
| 57 | + } |
| 58 | + } |
| 59 | +} |
| 60 | +``` |
| 61 | + |
| 62 | +**Add the dependency:** |
| 63 | + |
| 64 | +```kotlin |
| 65 | +testImplementation("com.trendyol:stove-testing-e2e-grpc:$version") |
| 66 | +``` |
| 67 | + |
| 68 | +--- |
| 69 | + |
| 70 | +### WebSocket Testing |
| 71 | + |
| 72 | +The HTTP system now supports WebSocket connections for testing real-time communication: |
| 73 | + |
| 74 | +```kotlin |
| 75 | +http { |
| 76 | + webSocket("/chat") { session -> |
| 77 | + session.send("Hello!") |
| 78 | + val response = session.receiveText() |
| 79 | + response shouldBe "Echo: Hello!" |
| 80 | + } |
| 81 | +} |
| 82 | + |
| 83 | +// With authentication |
| 84 | +http { |
| 85 | + webSocket( |
| 86 | + uri = "/secure-chat", |
| 87 | + headers = mapOf("X-Custom-Header" to "value"), |
| 88 | + token = "jwt-token".some() |
| 89 | + ) { session -> |
| 90 | + session.send("Authenticated message") |
| 91 | + } |
| 92 | +} |
| 93 | + |
| 94 | +// Collect multiple messages |
| 95 | +http { |
| 96 | + webSocketExpect("/notifications") { session -> |
| 97 | + val messages = session.collectTexts(count = 3) |
| 98 | + messages.size shouldBe 3 |
| 99 | + } |
| 100 | +} |
| 101 | +``` |
| 102 | + |
| 103 | +Available methods: |
| 104 | +- `webSocket` - Establish connection and interact |
| 105 | +- `webSocketExpect` - Assertion-focused testing |
| 106 | +- `webSocketRaw` - Direct access to underlying Ktor session |
| 107 | + |
| 108 | +--- |
| 109 | + |
| 110 | +### Partial Mocking for WireMock |
| 111 | + |
| 112 | +New partial matching methods allow mocking requests by matching only specific fields in the request body: |
| 113 | + |
| 114 | +```kotlin |
| 115 | +wiremock { |
| 116 | + // Match requests containing specific fields (ignores extra fields) |
| 117 | + mockPostContaining( |
| 118 | + url = "/api/orders", |
| 119 | + requestContaining = mapOf( |
| 120 | + "productId" to 123, |
| 121 | + "order.customer.id" to "cust-456" // Dot notation for nested fields |
| 122 | + ), |
| 123 | + statusCode = 201, |
| 124 | + responseBody = OrderResponse(id = "order-1").some() |
| 125 | + ) |
| 126 | +} |
| 127 | +``` |
| 128 | + |
| 129 | +**Features:** |
| 130 | +- **AND logic**: All specified fields must match |
| 131 | +- **Dot notation**: Access nested fields like `"order.customer.id"` |
| 132 | +- **Partial objects**: Nested objects match if they contain at least the specified fields |
| 133 | +- **Methods**: `mockPostContaining`, `mockPutContaining`, `mockPatchContaining` |
| 134 | + |
| 135 | +--- |
| 136 | + |
| 137 | +### Embedded Kafka Mode |
| 138 | + |
| 139 | +Run Kafka tests without Docker containers using embedded Kafka: |
| 140 | + |
| 141 | +```kotlin |
| 142 | +kafka { |
| 143 | + KafkaSystemOptions( |
| 144 | + useEmbeddedKafka = true, // No container needed |
| 145 | + configureExposedConfiguration = { cfg -> |
| 146 | + listOf("kafka.bootstrapServers=${cfg.bootstrapServers}") |
| 147 | + } |
| 148 | + ) |
| 149 | +} |
| 150 | +``` |
| 151 | + |
| 152 | +This is ideal for: |
| 153 | +- Self-contained integration tests |
| 154 | +- Faster test startup |
| 155 | +- Environments without Docker access |
| 156 | + |
| 157 | +--- |
| 158 | + |
| 159 | +## Improvements |
| 160 | + |
| 161 | +### Provided Instances (Testcontainer-less Mode) |
| 162 | + |
| 163 | +The following components now support connecting to externally managed infrastructure using the `provided()` companion function: |
| 164 | + |
| 165 | +| Component | Provided Instance Support | |
| 166 | +|---------------|:-------------------------:| |
| 167 | +| PostgreSQL | ✅ | |
| 168 | +| MSSQL | ✅ | |
| 169 | +| MongoDB | ✅ | |
| 170 | +| Couchbase | ✅ | |
| 171 | +| Elasticsearch | ✅ | |
| 172 | +| Redis | ✅ | |
| 173 | +| Kafka | ✅ | |
| 174 | + |
| 175 | +**PostgreSQL example:** |
| 176 | + |
| 177 | +```kotlin |
| 178 | +postgresql { |
| 179 | + PostgresqlOptions.provided( |
| 180 | + host = "external-db.example.com", |
| 181 | + port = 5432, |
| 182 | + databaseName = "testdb", |
| 183 | + username = "user", |
| 184 | + password = "pass", |
| 185 | + runMigrations = true, |
| 186 | + cleanup = { client -> client.execute("TRUNCATE users") }, |
| 187 | + configureExposedConfiguration = { cfg -> |
| 188 | + listOf("spring.datasource.url=${cfg.jdbcUrl}") |
| 189 | + } |
| 190 | + ) |
| 191 | +} |
| 192 | +``` |
| 193 | + |
| 194 | +**Kafka example:** |
| 195 | + |
| 196 | +```kotlin |
| 197 | +kafka { |
| 198 | + KafkaSystemOptions.provided( |
| 199 | + bootstrapServers = "kafka.example.com:9092", |
| 200 | + runMigrations = true, |
| 201 | + cleanup = { admin -> admin.deleteTopics(listOf("orders")) }, |
| 202 | + configureExposedConfiguration = { cfg -> |
| 203 | + listOf("kafka.bootstrapServers=${cfg.bootstrapServers}") |
| 204 | + } |
| 205 | + ) |
| 206 | +} |
| 207 | +``` |
| 208 | + |
| 209 | +This is useful for: |
| 210 | +- CI/CD pipelines with shared infrastructure |
| 211 | +- Reducing startup time by reusing existing instances |
| 212 | +- Lower memory/CPU usage by avoiding container overhead |
| 213 | + |
| 214 | +--- |
| 215 | + |
| 216 | +### Pause/Unpause Containers |
| 217 | + |
| 218 | +PostgreSQL, MSSQL, MongoDB, Couchbase, Elasticsearch, Redis, and Kafka now support pausing and unpausing containers for testing resilience scenarios: |
| 219 | + |
| 220 | +```kotlin |
| 221 | +postgresql { |
| 222 | + // Pause to simulate network issues |
| 223 | + pause() |
| 224 | + |
| 225 | + // Your application should handle the connection failure |
| 226 | + http { get<Response>("/health") { it.status shouldBe 503 } } |
| 227 | + |
| 228 | + // Unpause to restore connectivity |
| 229 | + unpause() |
| 230 | +} |
| 231 | +``` |
| 232 | + |
| 233 | +--- |
| 234 | + |
| 235 | +### Response Headers in WireMock |
| 236 | + |
| 237 | +WireMock now supports custom response headers: |
| 238 | + |
| 239 | +```kotlin |
| 240 | +wiremock { |
| 241 | + mockGet( |
| 242 | + url = "/api/users/123", |
| 243 | + statusCode = 200, |
| 244 | + responseBody = user.some(), |
| 245 | + responseHeaders = mapOf( |
| 246 | + "X-Request-Id" to "req-123", |
| 247 | + "X-Rate-Limit-Remaining" to "99" |
| 248 | + ) |
| 249 | + ) |
| 250 | +} |
| 251 | +``` |
| 252 | + |
| 253 | +--- |
| 254 | + |
| 255 | +### Documentation Improvements |
| 256 | + |
| 257 | +- Comprehensive documentation for all components |
| 258 | +- Updated examples matching actual API signatures |
| 259 | +- Added component feature matrix showing migration, cleanup, and provided instance support |
| 260 | +- FAQ section with common questions and answers |
| 261 | + |
| 262 | +--- |
| 263 | + |
| 264 | +## Dependency Updates |
| 265 | + |
| 266 | +- Kotlin 2.0.x |
| 267 | +- Kotest 6.0.0.Mx |
| 268 | +- Koin 4.x |
| 269 | +- Arrow 2.x |
| 270 | +- Testcontainers 2.x |
| 271 | +- Ktor 3.x |
| 272 | +- Various other dependency updates for security and compatibility |
| 273 | + |
| 274 | +--- |
| 275 | + |
| 276 | +## Migration Guide |
| 277 | + |
| 278 | +### From 0.18.x |
| 279 | + |
| 280 | +This release is backward compatible. New features are opt-in: |
| 281 | + |
| 282 | +| Feature | How to Enable | |
| 283 | +|---------|---------------| |
| 284 | +| gRPC testing | Add `stove-testing-e2e-grpc` dependency | |
| 285 | +| WebSocket testing | Use `http { webSocket("/path") { ... } }` | |
| 286 | +| Embedded Kafka | Set `useEmbeddedKafka = true` in `KafkaSystemOptions` | |
| 287 | +| Provided instances | Use `SystemOptions.provided(...)` instead of `SystemOptions(...)` | |
| 288 | +| Pause/Unpause | Call `system.pause()` and `system.unpause()` on container-based systems | |
| 289 | +| Partial WireMock mocking | Use `mockPostContaining`, `mockPutContaining`, `mockPatchContaining` | |
| 290 | +| Response headers | Pass `responseHeaders = mapOf(...)` to WireMock mock methods | |
| 291 | + |
| 292 | +### Breaking Changes |
| 293 | + |
| 294 | +None in this release. |
| 295 | + |
| 296 | +--- |
| 297 | + |
| 298 | +## Full Changelog |
| 299 | + |
| 300 | +See the [GitHub Releases](https://github.com/Trendyol/stove/releases) page for the complete list of commits and contributors. |
| 301 | + |
| 302 | +--- |
| 303 | + |
| 304 | +## Contributors |
| 305 | + |
| 306 | +Thanks to all contributors who made this release possible! |
| 307 | + |
| 308 | +--- |
| 309 | + |
| 310 | +## Getting Started |
| 311 | + |
| 312 | +```kotlin |
| 313 | +dependencies { |
| 314 | + testImplementation("com.trendyol:stove-testing-e2e:0.19.0") |
| 315 | + testImplementation("com.trendyol:stove-spring-testing-e2e:0.19.0") // or ktor, micronaut |
| 316 | + // Add component-specific dependencies as needed |
| 317 | + testImplementation("com.trendyol:stove-testing-e2e-rdbms-postgres:0.19.0") |
| 318 | + testImplementation("com.trendyol:stove-testing-e2e-kafka:0.19.0") |
| 319 | + testImplementation("com.trendyol:stove-testing-e2e-grpc:0.19.0") // NEW |
| 320 | +} |
| 321 | +``` |
| 322 | + |
| 323 | +For snapshot versions, add the snapshot repository: |
| 324 | + |
| 325 | +```kotlin |
| 326 | +repositories { |
| 327 | + maven("https://central.sonatype.com/repository/maven-snapshots") |
| 328 | +} |
| 329 | +``` |
0 commit comments