SkillAgentSearch skills...

Stove

Stove: The easiest way of writing e2e/component tests for your JVM back-end app with Kotlin

Install / Use

/learn @Trendyol/Stove

README

<h1 align="center">Stove</h1> <p align="center"> End-to-end testing framework for the JVM.<br/> Test your application against real infrastructure with a unified Kotlin DSL. </p> <p align="center"> <img src="https://img.shields.io/maven-central/v/com.trendyol/stove?versionPrefix=0&label=release&color=blue" alt="Release"/> <a href="https://github.com/Trendyol/homebrew-trendyol-tap"><img src="https://img.shields.io/github/v/release/Trendyol/stove?label=StoveCLI(homebrew)&logo=homebrew&color=FBB040" alt="Homebrew"/></a> <a href="https://central.sonatype.com/service/rest/repository/browse/maven-snapshots/com/trendyol/"><img src="https://img.shields.io/badge/dynamic/xml?url=https%3A%2F%2Fcentral.sonatype.com%2Frepository%2Fmaven-snapshots%2Fcom%2Ftrendyol%2Fstove%2Fmaven-metadata.xml&query=%2F%2Fmetadata%2Fversioning%2Flatest&label=snapshot&color=orange" alt="Snapshot"/></a> <a href="https://codecov.io/gh/Trendyol/stove"><img src="https://codecov.io/gh/Trendyol/stove/graph/badge.svg?token=HcKBT3chO7" alt="codecov"/></a> <a href="https://scorecard.dev/viewer/?uri=github.com/Trendyol/stove"><img src="https://img.shields.io/ossf-scorecard/github.com/Trendyol/stove?label=openssf%20scorecard&style=flat" alt="OpenSSF Scorecard"/></a> </p>
stove {
  // Call API and verify response
  http {
    postAndExpectBodilessResponse("/orders", body = CreateOrderRequest(userId, productId).some()) {
      it.status shouldBe 201
    }
  }

  // Verify database state
  postgresql {
    shouldQuery<Order>("SELECT * FROM orders WHERE user_id = '$userId'", mapper = { row ->
      Order(row.string("status"))
    }) {
      it.first().status shouldBe "CONFIRMED"
    }
  }

  // Verify event was published
  kafka {
    shouldBePublished<OrderCreatedEvent> {
      actual.userId == userId
    }
  }

  // Access application beans directly
  using<InventoryService> {
    getStock(productId) shouldBe 9
  }
}

https://github.com/user-attachments/assets/14597dc6-e9d4-43ab-8cfa-578ab3c3e6df

Why Stove?

The JVM ecosystem has excellent frameworks for building applications, but e2e testing remains fragmented. Testcontainers handles infrastructure, but you still write boilerplate for configuration, app startup, and assertions. Differently for each framework.

Stove explores how the testing experience on the JVM can be improved by unifying assertions and the supporting infrastructure. It creates a concise and expressive testing DSL by leveraging Kotlin's unique language features.

Stove works with Java, Kotlin, and Scala applications across Spring Boot, Ktor, Micronaut, and Quarkus. Because tests are framework-agnostic, teams can migrate between stacks without rewriting test code. It empowers developers to write clear assertions even for code that is traditionally hard to test (async flows, message consumers, database side effects).

What Stove does:

  • Starts containers via Testcontainers or connect provided infra (PostgreSQL, MySQL, Kafka, etc.)
  • Launches your actual application with test configuration
  • Exposes a unified DSL for assertions across all components
  • Provides access to your DI container from tests
  • Debug your entire use case with one click (breakpoints work everywhere)
  • Get code coverage from e2e test execution
  • Supports Spring Boot, Ktor, Micronaut, Quarkus
  • Extensible architecture for adding new components and frameworks (Writing Custom Systems)

Dashboard (New in 0.23.0)

Stove Dashboard introduces a local real-time dashboard for end-to-end test runs. It captures HTTP calls, Kafka activity, database assertions, and traces in one place so you can inspect successful and failed runs with full context.

Quick start

# 1) Install and start the Dashboard CLI
brew install Trendyol/trendyol-tap/stove
stove

# 2) Run your tests and open the dashboard
./gradlew test
# http://localhost:4040
dependencies {
  testImplementation(platform("com.trendyol:stove-bom:$version"))
  testImplementation("com.trendyol:stove-dashboard")
  testImplementation("com.trendyol:stove-tracing")
}

Stove()
  .with {
    dashboard { DashboardSystemOptions(appName = "product-api") }
    tracing { enableSpanReceiver() } // recommended
  }.run()

See Dashboard docs and 0.23.0 release notes for full details.

Getting Started

1. Add dependencies

dependencies {
  // Import BOM for version management
  testImplementation(platform("com.trendyol:stove-bom:$version"))
  
  // Core and framework starter
  testImplementation("com.trendyol:stove")
  testImplementation("com.trendyol:stove-spring")  // or stove-ktor, stove-micronaut, stove-quarkus
  
  // Component modules
  testImplementation("com.trendyol:stove-postgres")
  testImplementation("com.trendyol:stove-mysql")
  testImplementation("com.trendyol:stove-kafka")
}

Snapshots: As of 5th June 2025, Stove's snapshot packages are hosted on Central Sonatype.

repositories {
  maven("https://central.sonatype.com/repository/maven-snapshots")
}

2. Configure Stove (runs once before all tests)

class StoveConfig : AbstractProjectConfig() {
  override suspend fun beforeProject() = Stove()
    .with {
      httpClient {
        HttpClientSystemOptions(baseUrl = "http://localhost:8080")
      }
      postgresql {
        PostgresqlOptions(
          cleanup = { it.execute("TRUNCATE orders, users") },
          configureExposedConfiguration = { listOf("spring.datasource.url=${it.jdbcUrl}") }
        ).migrations {
          register<CreateUsersTable>()
        }
      }
      kafka {
        KafkaSystemOptions(
          cleanup = { it.deleteTopics(listOf("orders")) },
          configureExposedConfiguration = { listOf("kafka.bootstrapServers=${it.bootstrapServers}") }
        ).migrations {
          register<CreateOrdersTopic>()
        }
      }
      bridge()
      springBoot(runner = { params ->
        myApp.run(params) { addTestDependencies() }
      })
    }.run()

  override suspend fun afterProject() = Stove.stop()
}

3. Write tests

test("should process order") {
  stove {
    http {
      get<Order>("/orders/123") {
        it.status shouldBe "CONFIRMED"
      }
    }
    postgresql {
      shouldQuery<Order>("SELECT * FROM orders", mapper = { row ->
        Order(row.string("status"))
      }) {
        it.size shouldBe 1
      }
    }
    kafka {
      shouldBePublished<OrderCreatedEvent> {
        actual.orderId == "123"
      }
    }
  }
}

Writing Tests

All assertions happen inside stove { }. Each component has its own DSL block.

HTTP

http {
  get<User>("/users/$id") {
    it.name shouldBe "John"
  }
  postAndExpectBodilessResponse("/users", body = request.some()) {
    it.status shouldBe 201
  }
  postAndExpectBody<User>("/users", body = request.some()) {
    it.id shouldNotBe null
  }
}

Database

postgresql {  // also: mysql, mongodb, couchbase, mssql, elasticsearch, redis
  shouldExecute("INSERT INTO users (name) VALUES ('Jane')")
  shouldQuery<User>("SELECT * FROM users", mapper = { row ->
    User(row.string("name"))
  }) {
    it.size shouldBe 1
  }
}

Kafka

kafka {
  publish("orders.created", OrderCreatedEvent(orderId = "123"))
  shouldBeConsumed<OrderCreatedEvent> {
    actual.orderId == "123"
  }
  shouldBePublished<OrderConfirmedEvent> {
    actual.orderId == "123"
  }
}

External API Mocking

wiremock {
  mockGet("/external-api/users/1", responseBody = User(id = 1, name = "John").some())
  mockPost("/external-api/notify", statusCode = 202)
}

Application Beans

Access your DI container directly via bridge():

using<OrderService> { processOrder(orderId) }
using<UserRepo, EmailService> { userRepo, emailService ->
  userRepo.findById(id) shouldNotBe null
}

Reporting

When tests fail, Stove automatically enriches exceptions with a detailed execution report showing exactly what happened:

<details> <summary><strong>Example Report</strong></summary>
╔══════════════════════════════════════════════════════════════════════════════════════════════════╗
║                                   STOVE TEST EXECUTION REPORT                                    ║
║                                                                                                  ║
║ Test: should create new product when send product create request from api for the allowed        ║
║ supplier                                                                                         ║
║ ID: ExampleTest::should create new product when send product create request from api for the     ║
║ allowed supplier                                                                                 ║
║ Status: FAILED                                                                                   ║
╠══════════════════════════════════════════════════════════════════════════════════════════════════╣
║                                                                                                  ║
║ TIMELINE                                                                                         ║
║ ────────                                                                                         ║
║                                                                                                  ║
║ 12:41:12.371 ✓ PASSED [WireMock] Register stub: GET /suppliers/99/allowed                        ║
║     Output: kotlin.Unit                                                                          ║
║     Metadata: {statusCode=200, responseHeaders={}}                                               ║
║                                   

Related Skills

View on GitHub
GitHub Stars306
CategoryDevelopment
Updated1d ago
Forks17

Languages

Kotlin

Security Score

100/100

Audited on Mar 31, 2026

No findings