{"id":29179701,"url":"https://github.com/trendyol/stove","last_synced_at":"2026-01-26T15:01:24.721Z","repository":{"id":65438860,"uuid":"590452775","full_name":"Trendyol/stove","owner":"Trendyol","description":"Stove: The easiest way of writing e2e/component tests for your JVM back-end app with Kotlin","archived":false,"fork":false,"pushed_at":"2026-01-12T18:56:40.000Z","size":10902,"stargazers_count":295,"open_issues_count":5,"forks_count":17,"subscribers_count":12,"default_branch":"main","last_synced_at":"2026-01-12T20:17:43.783Z","etag":null,"topics":["component-testing","e2e-testing","integration-testing","java","kotlin","ktor","spring-boot","test","test-automation","testcontainers","testing","testing-framework","testing-tools"],"latest_commit_sha":null,"homepage":"https://trendyol.github.io/stove/","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Trendyol.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2023-01-18T13:03:34.000Z","updated_at":"2026-01-12T17:56:46.000Z","dependencies_parsed_at":"2026-01-07T05:04:09.421Z","dependency_job_id":null,"html_url":"https://github.com/Trendyol/stove","commit_stats":null,"previous_names":[],"tags_count":45,"template":false,"template_full_name":null,"purl":"pkg:github/Trendyol/stove","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Trendyol%2Fstove","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Trendyol%2Fstove/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Trendyol%2Fstove/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Trendyol%2Fstove/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Trendyol","download_url":"https://codeload.github.com/Trendyol/stove/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Trendyol%2Fstove/sbom","scorecard":{"id":346382,"data":{"date":"2025-08-18T03:01:03Z","repo":{"name":"github.com/Trendyol/stove","commit":"240df371d9e559534ad060c225d35ee38f36aed3"},"scorecard":{"version":"v5.2.1","commit":"ab2f6e92482462fe66246d9e32f642855a691dc1"},"score":5.9,"checks":[{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: jobLevel 'checks' permission set to 'write': .github/workflows/build.yml:26","Info: jobLevel 'contents' permission set to 'read': .github/workflows/gradle-publish-release.yml:12","Info: jobLevel 'contents' permission set to 'read': .github/workflows/gradle-publish-snapshot.yml:9","Warn: no topLevel permission defined: .github/workflows/build-recipes.yml:1","Warn: no topLevel permission defined: .github/workflows/build.yml:1","Warn: no topLevel permission defined: .github/workflows/gradle-publish-release.yml:1","Warn: no topLevel permission defined: .github/workflows/gradle-publish-snapshot.yml:1","Warn: no topLevel permission defined: .github/workflows/publish-to-ghpages.yml:1","Info: topLevel permissions set to 'read-all': .github/workflows/scorecard.yml:13"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#token-permissions"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#dangerous-workflow"}},{"name":"Maintained","score":10,"reason":"30 commit(s) and 2 issue activity found in the last 90 days -- score normalized to 10","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#maintained"}},{"name":"Dependency-Update-Tool","score":10,"reason":"update tool detected","details":["Info: detected update tool: RenovateBot: renovate.json:1"],"documentation":{"short":"Determines if the project uses a dependency update tool.","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#dependency-update-tool"}},{"name":"Code-Review","score":0,"reason":"Found 1/11 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#code-review"}},{"name":"Binary-Artifacts","score":8,"reason":"binaries present in source code","details":["Warn: binary detected: gradle/wrapper/gradle-wrapper.jar:1","Warn: binary detected: recipes/gradle/wrapper/gradle-wrapper.jar:1"],"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":1,"reason":"dependency not pinned by hash detected -- score normalized to 1","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build-recipes.yml:20: update your workflow using https://app.stepsecurity.io/secureworkflow/Trendyol/stove/build-recipes.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build-recipes.yml:26: update your workflow using https://app.stepsecurity.io/secureworkflow/Trendyol/stove/build-recipes.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/build-recipes.yml:29: update your workflow using https://app.stepsecurity.io/secureworkflow/Trendyol/stove/build-recipes.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build.yml:37: update your workflow using https://app.stepsecurity.io/secureworkflow/Trendyol/stove/build.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build.yml:43: update your workflow using https://app.stepsecurity.io/secureworkflow/Trendyol/stove/build.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/build.yml:46: update your workflow using https://app.stepsecurity.io/secureworkflow/Trendyol/stove/build.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/build.yml:67: update your workflow using https://app.stepsecurity.io/secureworkflow/Trendyol/stove/build.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/gradle-publish-release.yml:16: update your workflow using https://app.stepsecurity.io/secureworkflow/Trendyol/stove/gradle-publish-release.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/gradle-publish-release.yml:18: update your workflow using https://app.stepsecurity.io/secureworkflow/Trendyol/stove/gradle-publish-release.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/gradle-publish-release.yml:26: update your workflow using https://app.stepsecurity.io/secureworkflow/Trendyol/stove/gradle-publish-release.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/gradle-publish-snapshot.yml:13: update your workflow using https://app.stepsecurity.io/secureworkflow/Trendyol/stove/gradle-publish-snapshot.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/gradle-publish-snapshot.yml:15: update your workflow using https://app.stepsecurity.io/secureworkflow/Trendyol/stove/gradle-publish-snapshot.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/gradle-publish-snapshot.yml:23: update your workflow using https://app.stepsecurity.io/secureworkflow/Trendyol/stove/gradle-publish-snapshot.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/publish-to-ghpages.yml:15: update your workflow using https://app.stepsecurity.io/secureworkflow/Trendyol/stove/publish-to-ghpages.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/publish-to-ghpages.yml:26: update your workflow using https://app.stepsecurity.io/secureworkflow/Trendyol/stove/publish-to-ghpages.yml/main?enable=pin","Warn: pipCommand not pinned by hash: .github/workflows/publish-to-ghpages.yml:19","Info:   3 out of  12 GitHub-owned GitHubAction dependencies pinned","Info:   1 out of   7 third-party GitHubAction dependencies pinned","Info:   0 out of   1 pipCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#pinned-dependencies"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#vulnerabilities"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#cii-best-practices"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: Apache License 2.0: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#license"}},{"name":"Packaging","score":10,"reason":"packaging workflow detected","details":["Info: Project packages its releases by way of GitHub Actions.: .github/workflows/gradle-publish-release.yml:8"],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#packaging"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#signed-releases"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#fuzzing"}},{"name":"CI-Tests","score":-1,"reason":"internal error: internal error: Client.Repositories.ListCheckRunsForRef: error during graphqlHandler.setupCheckRuns: Although you appear to have the correct authorization credentials, the `bridgecrewio` organization has an IP allow list enabled, and your IP address is not permitted to access this resource.","details":null,"documentation":{"short":"Determines if the project runs tests before pull requests are merged.","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#ci-tests"}},{"name":"SAST","score":-1,"reason":"internal error: internal error: Client.Checks.ListCheckRunsForRef: error during graphqlHandler.setupCheckRuns: Although you appear to have the correct authorization credentials, the `bridgecrewio` organization has an IP allow list enabled, and your IP address is not permitted to access this resource.","details":null,"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#sast"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#branch-protection"}},{"name":"Contributors","score":10,"reason":"project has 4 contributing companies or organizations","details":["Info: found contributions from: DynamicTranslator, ddd-cqrs-es, stoveproject, trendyol"],"documentation":{"short":"Determines if the project has a set of contributors from multiple organizations (e.g., companies).","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#contributors"}}]},"last_synced_at":"2025-08-18T07:07:45.789Z","repository_id":65438860,"created_at":"2025-08-18T07:07:45.789Z","updated_at":"2025-08-18T07:07:45.789Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28781308,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-26T13:55:28.044Z","status":"ssl_error","status_checked_at":"2026-01-26T13:55:26.068Z","response_time":59,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["component-testing","e2e-testing","integration-testing","java","kotlin","ktor","spring-boot","test","test-automation","testcontainers","testing","testing-framework","testing-tools"],"created_at":"2025-07-01T19:06:07.237Z","updated_at":"2026-01-26T15:01:24.714Z","avatar_url":"https://github.com/Trendyol.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003eStove\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n  End-to-end testing framework for the JVM.\u003cbr/\u003e\n  Test your application against real infrastructure with a unified Kotlin DSL.\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://img.shields.io/maven-central/v/com.trendyol/stove?versionPrefix=0\u0026label=release\u0026color=blue\" alt=\"Release\"/\u003e\n  \u003ca href=\"https://central.sonatype.com/service/rest/repository/browse/maven-snapshots/com/trendyol/\"\u003e\u003cimg src=\"https://img.shields.io/badge/dynamic/xml?url=https%3A%2F%2Fcentral.sonatype.com%2Frepository%2Fmaven-snapshots%2Fcom%2Ftrendyol%2Fstove%2Fmaven-metadata.xml\u0026query=%2F%2Fmetadata%2Fversioning%2Flatest\u0026label=snapshot\u0026color=orange\" alt=\"Snapshot\"/\u003e\u003c/a\u003e\n  \u003ca href=\"https://codecov.io/gh/Trendyol/stove\"\u003e\u003cimg src=\"https://codecov.io/gh/Trendyol/stove/graph/badge.svg?token=HcKBT3chO7\" alt=\"codecov\"/\u003e\u003c/a\u003e\n  \u003ca href=\"https://scorecard.dev/viewer/?uri=github.com/Trendyol/stove\"\u003e\u003cimg src=\"https://img.shields.io/ossf-scorecard/github.com/Trendyol/stove?label=openssf%20scorecard\u0026style=flat\" alt=\"OpenSSF Scorecard\"/\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n```kotlin\nstove {\n  // Call API and verify response\n  http {\n    postAndExpectBodilessResponse(\"/orders\", body = CreateOrderRequest(userId, productId).some()) {\n      it.status shouldBe 201\n    }\n  }\n\n  // Verify database state\n  postgresql {\n    shouldQuery\u003cOrder\u003e(\"SELECT * FROM orders WHERE user_id = '$userId'\", mapper = { row -\u003e\n      Order(row.string(\"status\"))\n    }) {\n      it.first().status shouldBe \"CONFIRMED\"\n    }\n  }\n\n  // Verify event was published\n  kafka {\n    shouldBePublished\u003cOrderCreatedEvent\u003e {\n      actual.userId == userId\n    }\n  }\n\n  // Access application beans directly\n  using\u003cInventoryService\u003e {\n    getStock(productId) shouldBe 9\n  }\n}\n```\n\n## Why Stove?\n\nThe JVM ecosystem has excellent frameworks for building applications, but e2e testing remains fragmented. Testcontainers\nhandles infrastructure, but you still write boilerplate for configuration, app startup, and assertions. Differently for\neach framework.\n\nStove explores how the testing experience on the JVM can be improved by unifying assertions and the supporting\ninfrastructure. It creates a concise and expressive testing DSL by leveraging Kotlin's unique language features.\n\nStove works with Java, Kotlin, and Scala applications across Spring Boot, Ktor, and Micronaut. Because tests are\nframework-agnostic, teams can migrate between stacks without rewriting test code. It empowers developers to write clear\nassertions even for code that is traditionally hard to test (async flows, message consumers, database side effects).\n\n**What Stove does:**\n\n- Starts containers via Testcontainers or connect **provided** infra (PostgreSQL, Kafka, etc.)\n- Launches your **actual** application with test configuration\n- Exposes a unified DSL for assertions across all components\n- Provides access to your DI container from tests\n- Debug your entire use case with one click (breakpoints work everywhere)\n- Get code coverage from e2e test execution\n- Supports Spring Boot, Ktor, Micronaut\n- Extensible architecture for adding new components and\n  frameworks ([Writing Custom Systems](https://trendyol.github.io/stove/writing-custom-systems/))\n\n\u003cp align=\"center\"\u003e\u003cimg src=\"./docs/assets/stove_architecture.svg\" with=\"600\" /\u003e\u003c/p\u003e\n\n## Getting Started\n\n**1. Add dependencies**\n\n```kotlin\ndependencies {\n  // Import BOM for version management\n  testImplementation(platform(\"com.trendyol:stove-bom:$version\"))\n  \n  // Core and framework starter\n  testImplementation(\"com.trendyol:stove\")\n  testImplementation(\"com.trendyol:stove-spring\")  // or stove-ktor, stove-micronaut\n  \n  // Component modules\n  testImplementation(\"com.trendyol:stove-postgres\")\n  testImplementation(\"com.trendyol:stove-kafka\")\n}\n```\n\n\u003e **Snapshots:** As of 5th June 2025, Stove's snapshot packages are hosted on [Central Sonatype](https://central.sonatype.com/service/rest/repository/browse/maven-snapshots/com/trendyol/).\n\u003e ```kotlin\n\u003e repositories {\n\u003e   maven(\"https://central.sonatype.com/repository/maven-snapshots\")\n\u003e }\n\u003e ```\n\n**2. Configure Stove** (runs once before all tests)\n\n```kotlin\nclass TestConfig : AbstractProjectConfig() {\n  override suspend fun beforeProject() = Stove()\n    .with {\n      httpClient {\n        HttpClientSystemOptions(baseUrl = \"http://localhost:8080\")\n      }\n      postgresql {\n        PostgresqlOptions(\n          cleanup = { it.execute(\"TRUNCATE orders, users\") },\n          configureExposedConfiguration = { listOf(\"spring.datasource.url=${it.jdbcUrl}\") }\n        ).migrations {\n          register\u003cCreateUsersTable\u003e()\n        }\n      }\n      kafka {\n        KafkaSystemOptions(\n          cleanup = { it.deleteTopics(listOf(\"orders\")) },\n          configureExposedConfiguration = { listOf(\"kafka.bootstrapServers=${it.bootstrapServers}\") }\n        ).migrations {\n          register\u003cCreateOrdersTopic\u003e()\n        }\n      }\n      bridge()\n      springBoot(runner = { params -\u003e\n        myApp.run(params) { addTestDependencies() }\n      })\n    }.run()\n\n  override suspend fun afterProject() = Stove.stop()\n}\n```\n\n**3. Write tests**\n\n```kotlin\ntest(\"should process order\") {\n  stove {\n    http {\n      get\u003cOrder\u003e(\"/orders/123\") {\n        it.status shouldBe \"CONFIRMED\"\n      }\n    }\n    postgresql {\n      shouldQuery\u003cOrder\u003e(\"SELECT * FROM orders\", mapper = { row -\u003e\n        Order(row.string(\"status\"))\n      }) {\n        it.size shouldBe 1\n      }\n    }\n    kafka {\n      shouldBePublished\u003cOrderCreatedEvent\u003e {\n        actual.orderId == \"123\"\n      }\n    }\n  }\n}\n```\n\n## Writing Tests\n\nAll assertions happen inside `stove { }`. Each component has its own DSL block.\n\n### HTTP\n\n```kotlin\nhttp {\n  get\u003cUser\u003e(\"/users/$id\") {\n    it.name shouldBe \"John\"\n  }\n  postAndExpectBodilessResponse(\"/users\", body = request.some()) {\n    it.status shouldBe 201\n  }\n  postAndExpectBody\u003cUser\u003e(\"/users\", body = request.some()) {\n    it.id shouldNotBe null\n  }\n}\n```\n\n### Database\n\n```kotlin\npostgresql {  // also: mongodb, couchbase, mssql, elasticsearch, redis\n  shouldExecute(\"INSERT INTO users (name) VALUES ('Jane')\")\n  shouldQuery\u003cUser\u003e(\"SELECT * FROM users\", mapper = { row -\u003e\n    User(row.string(\"name\"))\n  }) {\n    it.size shouldBe 1\n  }\n}\n```\n\n### Kafka\n\n```kotlin\nkafka {\n  publish(\"orders.created\", OrderCreatedEvent(orderId = \"123\"))\n  shouldBeConsumed\u003cOrderCreatedEvent\u003e {\n    actual.orderId == \"123\"\n  }\n  shouldBePublished\u003cOrderConfirmedEvent\u003e {\n    actual.orderId == \"123\"\n  }\n}\n```\n\n### External API Mocking\n\n```kotlin\nwiremock {\n  mockGet(\"/external-api/users/1\", responseBody = User(id = 1, name = \"John\").some())\n  mockPost(\"/external-api/notify\", statusCode = 202)\n}\n```\n\n### Application Beans\n\nAccess your DI container directly via `bridge()`:\n\n```kotlin\nusing\u003cOrderService\u003e { processOrder(orderId) }\nusing\u003cUserRepo, EmailService\u003e { userRepo, emailService -\u003e\n  userRepo.findById(id) shouldNotBe null\n}\n```\n\n### Reporting\n\nWhen tests fail, Stove automatically enriches exceptions with a detailed execution report showing exactly what happened:\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eExample Report\u003c/strong\u003e\u003c/summary\u003e\n\n```\n╔══════════════════════════════════════════════════════════════════════════════════════════════════╗\n║                                   STOVE TEST EXECUTION REPORT                                    ║\n║                                                                                                  ║\n║ Test: should create new product when send product create request from api for the allowed        ║\n║ supplier                                                                                         ║\n║ ID: ExampleTest::should create new product when send product create request from api for the     ║\n║ allowed supplier                                                                                 ║\n║ Status: FAILED                                                                                   ║\n╠══════════════════════════════════════════════════════════════════════════════════════════════════╣\n║                                                                                                  ║\n║ TIMELINE                                                                                         ║\n║ ────────                                                                                         ║\n║                                                                                                  ║\n║ 12:41:12.371 ✓ PASSED [WireMock] Register stub: GET /suppliers/99/allowed                        ║\n║     Output: kotlin.Unit                                                                          ║\n║     Metadata: {statusCode=200, responseHeaders={}}                                               ║\n║                                                                                                  ║\n║ 12:41:13.405 ✓ PASSED [HTTP] POST /api/product/create                                            ║\n║     Input: ProductCreateRequest(id=1, name=product name, supplierId=99)                          ║\n║     Output: kotlin.Unit                                                                          ║\n║     Metadata: {status=200, headers={}}                                                           ║\n║                                                                                                  ║\n║ 12:41:13.424 ✓ PASSED [Kafka] shouldBePublished\u003cProductCreatedEvent\u003e                             ║\n║     Output: ProductCreatedEvent(id=1, name=product name, supplierId=99, createdDate=Thu Jan 08   ║\n║     12:41:12 CET 2026, type=ProductCreatedEvent)                                                 ║\n║     Metadata: {timeout=5s}                                                                       ║\n║                                                                                                  ║\n║ 12:41:13.455 ✗ FAILED [Couchbase] Get document                                                   ║\n║     Input: {id=product:1}                                                                        ║\n║     Error: expected:\u003c100L\u003e but was:\u003c99L\u003e                                                         ║\n║                                                                                                  ║\n╠══════════════════════════════════════════════════════════════════════════════════════════════════╣\n║                                                                                                  ║\n║ SYSTEM SNAPSHOTS                                                                                 ║\n║ ────────────────                                                                                 ║\n║                                                                                                  ║\n║ ┌─ HTTP ──────────────────────────────────────────────────────────────────────────────────────── ║\n║                                                                                                  ║\n║   No detailed state available                                                                    ║\n║                                                                                                  ║\n║ ┌─ COUCHBASE ─────────────────────────────────────────────────────────────────────────────────── ║\n║                                                                                                  ║\n║   No detailed state available                                                                    ║\n║                                                                                                  ║\n║ ┌─ KAFKA ─────────────────────────────────────────────────────────────────────────────────────── ║\n║                                                                                                  ║\n║   Consumed: 0                                                                                    ║\n║   Published: 1                                                                                   ║\n║   Committed: 0                                                                                   ║\n║                                                                                                  ║\n║   State Details:                                                                                 ║\n║     consumed: 0 item(s)                                                                          ║\n║     published: 1 item(s)                                                                         ║\n║       [0]                                                                                        ║\n║         id: 376db940-a367-4419-a628-4754c9466421                                                 ║\n║         topic: stove-standalone-example.productCreated.1                                         ║\n║         key: 1                                                                                   ║\n║         headers: {X-EventType=ProductCreatedEvent, X-MessageId=29902970-056d-4ae9-9a84-...}      ║\n║         message: {\"id\":1,\"name\":\"product name\",\"supplierId\":99,...}                              ║\n║     committed: 0 item(s)                                                                         ║\n║                                                                                                  ║\n║ ┌─ WIREMOCK ──────────────────────────────────────────────────────────────────────────────────── ║\n║                                                                                                  ║\n║   Registered stubs: 0                                                                            ║\n║   Served requests: 0 (matched: 0)                                                                ║\n║   Unmatched requests: 0                                                                          ║\n║                                                                                                  ║\n╚══════════════════════════════════════════════════════════════════════════════════════════════════╝\n```\n\n\u003c/details\u003e\n\n**Features:**\n- Timeline of all operations with timestamps and results\n- Input/output for each action\n- Expected vs actual values on failures\n- System snapshots (Kafka messages, WireMock stubs, etc.)\n\n**Test Framework Extensions:**\n\nUse the provided extensions to automatically enrich failures:\n\n```kotlin\n// Kotest - register in project config\nclass TestConfig : AbstractProjectConfig() {\n  override val extensions = listOf(StoveKotestExtension())\n}\n\n// JUnit 5 - annotate test class\n@ExtendWith(StoveJUnitExtension::class)\nclass MyTest { ... }\n```\n\n**Configuration:**\n\n```kotlin\nStove(\n  StoveOptions(\n    reportingEnabled = true,           // Enable/disable reporting (default: true)\n    dumpReportOnTestFailure = true,    // Enrich failures with report (default: true)\n    failureRenderer = PrettyConsoleRenderer  // Custom renderer (default: PrettyConsoleRenderer)\n  )\n).with { ... }\n```\n\n## Configuration\n\n### Framework Setup\n\n\u003ctable\u003e\n\u003ctr\u003e\u003cth\u003eSpring Boot\u003c/th\u003e\u003cth\u003eKtor\u003c/th\u003e\u003cth\u003eMicronaut\u003c/th\u003e\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\n\n```kotlin\nspringBoot(\n  runner = { params -\u003e\n    myApp.run(params) {\n      addTestDependencies()\n    }\n  }\n)\n```\n\n\u003c/td\u003e\n\u003ctd\u003e\n\n```kotlin\nktor(\n  runner = { params -\u003e\n    myApp.run(params) {\n      addTestDependencies()\n    }\n  }\n)\n```\n\n\u003c/td\u003e\n\u003ctd\u003e\n\n```kotlin\nmicronaut(\n  runner = { params -\u003e\n    myApp.run(params) {\n      addTestDependencies()\n    }\n  }\n)\n```\n\n\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/table\u003e\n\n### Container Reuse\n\nSpeed up local development by keeping containers running between test runs:\n\n```kotlin\nStove { keepDependenciesRunning() }.with { ... }\n```\n\n### Cleanup\n\nRun cleanup logic after tests complete:\n\n```kotlin\npostgresql {\n  PostgresqlOptions(cleanup = { it.execute(\"TRUNCATE users\") }, ...)\n}\n\nkafka {\n  KafkaSystemOptions(cleanup = { it.deleteTopics(listOf(\"test-topic\")) }, ...)\n}\n```\n\nAvailable for Kafka, PostgreSQL, MongoDB, Couchbase, MSSQL, Elasticsearch, Redis.\n\n### Migrations\n\nRun database migrations before tests start:\n\n```kotlin\npostgresql {\n  PostgresqlOptions(...)\n  .migrations {\n  register\u003cCreateUsersTable\u003e()\n  register\u003cCreateOrdersTable\u003e()\n}\n}\n```\n\nAvailable for Kafka, PostgreSQL, MongoDB, Couchbase, MSSQL, Elasticsearch, Redis.\n\n### Provided Instances\n\nConnect to existing infrastructure instead of starting containers (useful for CI/CD):\n\n```kotlin\npostgresql { PostgresqlOptions.provided(jdbcUrl = \"jdbc:postgresql://ci-db:5432/test\", ...) }\nkafka { KafkaSystemOptions.provided(bootstrapServers = \"ci-kafka:9092\", ...) }\n```\n\n\u003e **Tip:** When using provided instances, use migrations to create isolated test schemas and cleanups to remove test\n\u003e data afterwards. This ensures test isolation on shared infrastructure.\n\n\u003cstrong\u003eComplete Example\u003c/strong\u003e\n\n```kotlin\ntest(\"should create order with payment processing\") {\n  stove {\n    val userId = UUID.randomUUID().toString()\n    val productId = UUID.randomUUID().toString()\n\n    // 1. Seed database\n    postgresql {\n      shouldExecute(\"INSERT INTO users (id, name) VALUES ('$userId', 'John')\")\n      shouldExecute(\"INSERT INTO products (id, price, stock) VALUES ('$productId', 99.99, 10)\")\n    }\n\n    // 2. Mock external payment API\n    wiremock {\n      mockPost(\n        \"/payments/charge\", statusCode = 200,\n        responseBody = PaymentResult(success = true).some()\n      )\n    }\n\n    // 3. Call API\n    http {\n      postAndExpectBody\u003cOrderResponse\u003e(\n        \"/orders\",\n        body = CreateOrderRequest(userId, productId).some()\n      ) {\n        it.status shouldBe 201\n      }\n    }\n\n    // 4. Verify database\n    postgresql {\n      shouldQuery\u003cOrder\u003e(\"SELECT * FROM orders WHERE user_id = '$userId'\", mapper = { row -\u003e\n        Order(row.string(\"status\"))\n      }) {\n        it.first().status shouldBe \"CONFIRMED\"\n      }\n    }\n\n    // 5. Verify event published\n    kafka {\n      shouldBePublished\u003cOrderCreatedEvent\u003e {\n        actual.userId == userId\n      }\n    }\n\n    // 6. Verify via application service\n    using\u003cInventoryService\u003e { getStock(productId) shouldBe 9 }\n  }\n}\n```\n\n## Reference\n\n### Supported Components\n\n| Category   | Components                                                  |\n|------------|-------------------------------------------------------------|\n| Databases  | PostgreSQL, MongoDB, Couchbase, MSSQL, Elasticsearch, Redis |\n| Messaging  | Kafka                                                       |\n| HTTP       | Built-in client, WebSockets, WireMock                       |\n| gRPC       | Client (grpc-kotlin), Mock Server (native)                  |\n| Frameworks | Spring Boot, Ktor, Micronaut, Quarkus (experimental)        |\n\n### Feature Matrix\n\n| Component     | Migrations | Cleanup | Provided Instance | Pause/Unpause |\n|---------------|:----------:|:-------:|:-----------------:|:-------------:|\n| PostgreSQL    |     ✅      |    ✅    |         ✅         |       ✅       |\n| MSSQL         |     ✅      |    ✅    |         ✅         |       ✅       |\n| MongoDB       |     ✅      |    ✅    |         ✅         |       ✅       |\n| Couchbase     |     ✅      |    ✅    |         ✅         |       ✅       |\n| Elasticsearch |     ✅      |    ✅    |         ✅         |       ✅       |\n| Redis         |     ✅      |    ✅    |         ✅         |       ✅       |\n| Kafka         |     ✅      |    ✅    |         ✅         |       ✅       |\n| WireMock      |    n/a     |   n/a   |        n/a        |      n/a      |\n| HTTP Client   |    n/a     |   n/a   |        n/a        |      n/a      |\n| gRPC Mock     |    n/a     |   n/a   |        n/a        |      n/a      |\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eFAQ\u003c/strong\u003e\u003c/summary\u003e\n\n**Can I use Stove with Java applications?**  \nYes. Your application can be Java, Scala, or any JVM language. Tests are written in Kotlin for the DSL.\n\n**Does Stove replace Testcontainers?**  \nNo. Stove uses Testcontainers underneath and adds the unified DSL on top.\n\n**How slow is the first run?**  \nFirst run pulls Docker images (~1-2 min). Use `keepDependenciesRunning()` for instant subsequent runs.\n\n**Can I run tests in parallel?**  \nYes, with unique test data per test.\nSee [provided instances docs](https://trendyol.github.io/stove/Components/11-provided-instances/).\n\n\u003c/details\u003e\n\n## Resources\n\n- **[Documentation](https://trendyol.github.io/stove/)**: Full guides and API reference\n- **[Examples](https://github.com/Trendyol/stove/tree/main/examples)**: Working sample projects\n- **[Blog Post](https://medium.com/trendyol-tech/a-new-approach-to-the-api-end-to-end-testing-in-kotlin-f743fd1901f5)**:\n  Motivation and design decisions\n- **[Video Walkthrough](https://youtu.be/DJ0CI5cBanc?t=669)**: Live demo (Turkish)\n\n## Community\n\n**Used by:**\n\n1. [Trendyol](https://www.trendyol.com): Leading e-commerce platform, Turkey\n\n*Using Stove? Open a PR to add your company.*\n\n**Contributions:** [Issues](https://github.com/Trendyol/stove/issues) and PRs welcome  \n**License:** Apache 2.0\n\n\u003e **Note:** Production-ready and used at scale. API still evolving; breaking changes possible in minor releases with\n\u003e migration guides.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftrendyol%2Fstove","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftrendyol%2Fstove","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftrendyol%2Fstove/lists"}