Skip to content
/ stove Public

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

License

Notifications You must be signed in to change notification settings

Trendyol/stove

Repository files navigation

Stove

❝Where the infrastructure burns down to pure configurations(ashes)❞

Release codecov

What is Stove?

Stove is an end-to-end testing framework that simplifies testing by managing physical dependencies and your application in a unified way. Write infrastructure-agnostic but component-aware tests in Kotlin, regardless of your JVM-based tech stack.

Key Features

  • πŸš€ Zero Boilerplate: Write clean, focused tests without infrastructure setup code
  • πŸ”Œ Pluggable Architecture: Easily extend with custom infrastructure components
  • 🐳 Docker-Based: Leverages Testcontainers for reliable, isolated test environments
  • 🌐 Framework Agnostic: Works with Spring, Ktor, and other JVM frameworks (up for grabs)
  • πŸ”„ Physical Dependencies: Built-in support for Kafka, Couchbase, PostgreSQL, and more
  • ⚑ Fast Development: On top of the boilerplate free testing, optional Reuse test containers for blazing-fast local development

Supported Infrastructure

Physical dependencies:

  • βœ… Kafka
  • βœ… Couchbase
  • βœ… PostgreSQL
  • βœ… ElasticSearch
  • βœ… MongoDB
  • βœ… MSSQL
  • βœ… Redis
  • βœ… HTTP Client
  • βœ… WireMock

Frameworks:

  • βœ… Spring
  • βœ… Ktor
  • 🚧 Quarkus (up for grabs)
  • 🚧 Micronaut (up for grabs)

Quick Start

Add the dependency

// Add the following dependencies to your build.gradle.kts
testImplementation("com.trendyol:stove-testing-e2e:${version}")

// And the any of the following for the infrastructure you want to use, for example Kafka
// you can also use Couchbase, PostgreSQL, ElasticSearch, MongoDB, MSSQL, Redis, HTTP Client, WireMock
// as much as you want
testImplementation("com.trendyol:stove-testing-e2e-kafka:${version}")

// And Application Under Test (AUT)
testImplementation("com.trendyol:stove-ktor-testing-e2e:${version}")

// Or
testImplementation("com.trendyol:stove-spring-testing-e2e:${version}")

Set Up the TestSystem

TestSystem() {
  if (isRunningLocally()) {
    enableReuseForTestContainers()

    // this will keep the dependencies running
    // after the tests are finished,
    // so next run will be blazing fast :)
    keepDendenciesRunning()
  }
}.with {
  // Enables http client 
  // to make real http calls 
  // against the application under test
  httpClient {
    HttpClientSystemOptions(
      baseUrl = "http://localhost:8001",
    )
  }

  // Enables Couchbase physically 
  // and exposes the configuration 
  // to the application under test
  couchbase {
    CouchbaseSystemOptions(
      defaultBucket = "Stove",
      configureExposedConfiguration = { cfg -> listOf("couchbase.hosts=${cfg.hostsWithPort}") },
    )
  }

  // Enables Kafka physically 
  // and exposes the configuration 
  // to the application under test
  kafka {
    KafkaSystemOptions(
      configureExposedConfiguration = { cfg -> listOf("kafka.bootstrapServers=${cfg.boostrapServers}") },
    )
  }

  // Enables Wiremock on the given port 
  // and provides configurable mock HTTP server 
  // for your external API calls
  wiremock {
    WireMockSystemOptions(
      port = 9090,
      removeStubAfterRequestMatched = true,
      afterRequest = { e, _, _ ->
        logger.info(e.request.toString())
      },
    )
  }

  // The Application Under Test. 
  // Enables Spring Boot application 
  // to be run with the given parameters.
  springBoot(
    runner = { parameters ->
      stove.spring.example.run(parameters) { it.addTestSystemDependencies() }
    },
    withParameters = listOf(
      "server.port=8001",
      "logging.level.root=warn",
      "logging.level.org.springframework.web=warn",
      "spring.profiles.active=default",
      "kafka.heartbeatInSeconds=2",
    ),
  )
}.run()

Write Tests

TestSystem.validate {
  wiremock {
    mockGet("/example-url", responseBody = None, statusCode = 200)
  }

  http {
    get<String>("/hello/index") { actual ->
      actual shouldContain "Hi from Stove framework"
      println(actual)
    }
  }

  couchbase {
    shouldQuery<Any>("SELECT * FROM system:keyspaces") { actual ->
      println(actual)
    }
  }

  kafka {
    shouldBePublished<ExampleMessage> {
      actual.aggregateId == 123
          && metadata.topic = "example-topic"
          && metadata.headers["example-header"] == "example-value"
    }
    shouldBeConsumed<ExampleMessage> {
      actual.aggregateId == 123
          && metadata.topic = "example-topic"
          && metadata.headers["example-header"] == "example-value"
    }
  }

  couchbase {
    save(collection = "Backlogs", id = "id-of-backlog", instance = Backlog("id-of-backlog"))
  }

  http {
    postAndExpectBodilessResponse("/backlog/reserve") { actual ->
      actual.status.shouldBe(200)
    }
  }

  kafka {
    shouldBeConsumed<ProductCreated> {
      actual.aggregateId == expectedId
    }
  }
}

Why Stove?

The JVM ecosystem lacks a unified approach to end-to-end testing. While tools like Testcontainers exist, developers still need to:

  • Write extensive boilerplate code
  • Complex setup code for each tech stack
  • Create different testing setups for each framework
  • Manage complex infrastructure configurations for each framework

This affects teams across many tech stacks:

  • Kotlin with Spring Boot/Ktor
  • Java with Spring Boot/Micronaut/Quarkus
  • Scala with Spring Boot

Stove solves these challenges by providing:

  • A unified testing API across all JVM stacks
  • Built-in support for common infrastructure
  • Clean, Kotlin-based test syntax
  • Reusable test containers for fast local development

Stove unifies the testing experience across all JVM stacks, making it easier to write clean, focused tests.

Resources

Status

Warning

While Stove is production-ready and extensively used, the API is not yet fully stabilized. Breaking changes may occur in minor releases, but migration guides will always be provided.

Contributing

Contributions are welcome! Whether it's:

License

Stove is licensed under the Apache License, Version 2.0. See LICENSE for the full license text.