KTestify is an open-source, modular framework for integration-testing Apache Kafka and Kafka Streams pipelines. Its transport-agnostic core provides message matching and a fluent Java API, while the standalone Cucumber runner lets you describe scenarios in Gherkin. Plugins extend those scenarios to integrations such as HTTP and Azure Blob Storage.
|
The foundation library. A fluent Java API for testing Kafka producers, consumers, and Kafka Streams topologies, with no Cucumber dependency. What it gives you:
ConsumerContext<String, String> ctx = ConsumerContext.<String, String>builder()
.topic(outputTopic)
.matchMethod(ConfigConstants.methodMatchFile)
.matchFilePaths(List.of("expected/order.json"))
.readTimeout(Duration.ofSeconds(30))
.build();
Boolean passed = new RawKafkaConsumer(ctx).call();
assertTrue(passed); |
A standalone BDD test runner. Uses Cucumber 7 and Gherkin to express Kafka scenarios in plain language, backed by the ktestify-core API. Run it with Docker or as a fat JAR; it is not an embeddable Cucumber library. What it gives you:
The runner connects to a Kafka test cluster that you provide. It does not start a broker for you. Feature: Order enrichment
Background:
Given namespace
| namespace |
| ktestify |
Given input topic
| topicName | topicAlias |
| raw-roundtrip | orders-in |
Given output topic
| topicName | topicAlias |
| raw-roundtrip | orders-out |
Scenario: Order enrichment, JSON file match
When record from file is sent
| topicName | file | recordKey |
| raw-roundtrip | data/order.json | order-123 |
Then expected record from file
| topicAlias | file | expectedRecordKey |
| orders-out | expected/order.json | order-123 | |
KTestify plugins use the Service Provider Interface (SPI) to add integrations without modifying the core codebase. First-party plugins add transports and their own Gherkin steps, and are discovered automatically by the runner.
See the Plugin Development Guide to create a plugin.
These first-party plugins are ready to use with ktestify-cucumber.
|
Cross-platform test suite notifications for ktestify-cucumber. Sends a rich summary card at the end of each test run, with per-tag group breakdowns, CI/Git context, and configurable success-rate thresholds. What it gives you:
ktestify.plugins.notifications {
enabled = true
channels = [
{
type = "teams"
enabled = true
webhook-url = ${?KTESTIFY_TEAMS_WEBHOOK_URL}
on-failure-only = false
}
]
} |
Azure Blob Storage transport support for ktestify-cucumber. Ready-to-use Gherkin steps for uploading and asserting blobs are discovered automatically. What it gives you:
ktestify.plugins.azure-blob {
connection-string = ${?AZURE_STORAGE_CONNECTION_STRING}
request-timeout = 30s
auto-create-containers = true
} |
|
Synchronous HTTP request/response support for ktestify-cucumber. Its Gherkin steps send requests and assert response status, headers, and bodies. The plugin uses the JDK HTTP client and is discovered automatically through SPI. Given HTTP endpoint
| endpointAlias | baseUrl |
| orders-api | http://localhost:8080/api |
When HTTP request is sent
| endpointAlias | method | path | file | responseAlias |
| orders-api | POST | /orders | order.json | order-response |
Then expected HTTP response status
| responseAlias | statusCode |
| order-response | 200 | |
|
KTestify enforces a strict three-layer separation so that transports, orchestration logic, and assertion strategies are
completely decoupled. ConsumedRecord<V> is the only data type that crosses layer boundaries. The diagram below shows
the Kafka record path; synchronous plugins such as HTTP use the request/response transport contract.
flowchart TB
subgraph cucumber["🥒 ktestify-cucumber"]
GS["Gherkin Scenarios"]
SD["Step Definitions"]
CVS["ConsumerValidationService\nProducerValidationService"]
end
subgraph core["🟢 ktestify-core"]
subgraph api["Fluent API"]
CC["ConsumerContext"]
PC["ProducerContext"]
end
subgraph transport["Transport Layer"]
KRF["KafkaRecordFetcher"]
end
subgraph orchestration["Orchestration Layer"]
AKC["AbstractKafkaConsumer"]
RKC["RawKafkaConsumer"]
AVC["AvroKafkaConsumer"]
end
subgraph assertion["Assertion Layer"]
RM["RecordMatcher 〈interface〉"]
FRM["FileRecordMatcher"]
XML["XmlRecordMatcher"]
XP["XPathRecordMatcher"]
AVM["AvroFileRecordMatcher"]
NOOP["NoOpRecordMatcher"]
end
CR(["ConsumedRecord<V>\n- the only cross-layer type"])
end
GS --> SD --> CVS
CVS --> CC & PC
CC --> AKC
AKC --> RKC & AVC
RKC & AVC --> KRF
KRF -->|" List<ConsumedRecord<V>> "| CR
CR --> RM
RM --> FRM & XML & XP & AVM & NOOP
style cucumber fill: #0C1018, stroke: #2DD4BF, color: #e2e8f0
style core fill: #0C1018, stroke: #6EE7B7, color: #e2e8f0
style transport fill: #111827, stroke: #6EE7B7, color: #e2e8f0
style orchestration fill: #111827, stroke: #6EE7B7, color: #e2e8f0
style assertion fill: #111827, stroke: #6EE7B7, color: #e2e8f0
style api fill: #111827, stroke: #2DD4BF, color: #e2e8f0
style CR fill: #1e3a2f, stroke: #6EE7B7, color: #6EE7B7
| Match strategy | Raw (String) | Avro (GenericRecord) |
|---|---|---|
| JSON file comparison | ✅ | ✅ |
| Key + value file comparison | ✅ | ✅ |
| Positional fields (line/from/to) | ✅ | ✅ |
| XML structural comparison | ✅ | X |
| XPath expression matching | ✅ | X |
| Record key only | ✅ | ✅ |
| Batch (N records vs N files) | ✅ | ✅ |
| No-op (presence check) | ✅ | ✅ |
Use a dedicated Kafka test cluster, not a shared or production cluster. Configure Schema Registry as well if your tests use Avro. See the CI environment guide for setup details.
Create an application.conf for the runner:
ktestify {
kafka.bootstrap-servers = "kafka:9092"
kafka.topic-namespace = "my-app"
schema-registry.url = "http://schema-registry:8081"
framework.timeouts {
default-read-timeout = 30s
consumer-delta-time = 60s
}
framework.directories.assets = "/workspace/assets"
}See the configuration reference for defaults and environment variable names.
The recommended way to run a suite is with Docker:
docker run --rm \
-v "$(pwd)/features:/workspace/features" \
-v "$(pwd)/assets:/workspace/assets" \
-v "$(pwd)/config:/workspace/config" \
ghcr.io/ktestify/ktestify-cucumber:latest \
--config /workspace/config/application.conf \
/workspace/featuresThe Kafka address must be reachable from the container. The runner is also available as a fat JAR. See the installation guide for configuration-file, report, and tag-filtering examples.
The runner includes Kafka setup, producer, and consumer steps. This example shows valid DataTables for a raw record roundtrip:
Given input topic
| topicName | topicAlias | namespace | namespaceAlias |
| orders-in | orders-in | | |
Given output topic
| topicName | topicAlias | namespace | namespaceAlias |
| orders-out | orders-out | | |
Given assets directory
| absolutePath |
| src/test/resources/assets |
When record from file is sent
| topicName | file | recordKey |
| orders-in | order.json | order-123 |
Then expected record from file
| topicAlias | file | expectedRecordKey |
| orders-out | expected/order.json | order-123 |Other built-in Kafka validations include positional fields, XML, XPath, Avro, batch comparisons, and positive or negative record-presence checks. Key matching supports exact, contains, starts-with, ends-with, and regular expression strategies. See the complete step reference for all step phrases and DataTable columns.
| KTestify | Plain Kafka Clients | EmbeddedKafka | |
|---|---|---|---|
| Fluent assertion DSL | ✅ | ❌ | ❌ |
| BDD / Gherkin support | ✅ | ❌ | ❌ |
| Avro + Schema Registry | ✅ | ❌ | |
| XML / XPath matchers | ✅ | ❌ | ❌ |
| Batch record assertions | ✅ | ❌ | ❌ |
| Dynamic variables in payloads | ✅ | ❌ | ❌ |
| Requires a separate test broker | ✅ | ✅ | ❌ |
| Transport-agnostic design | ✅ | ❌ | ❌ |
| CI-friendly | ✅ | ✅ | ✅ |
- Full documentation
- Parent POM and dependency BOM
- Maven archetype for creating plugins
- Documentation site source
Contributions are welcome! Please read the contributing guide in each repository before opening a pull request.
- Fork the repository
- Create a feature branch:
git switch -c feat/my-feature - Commit using Conventional Commits:
git commit -m 'feat: add my feature' - Push and open a Pull Request against
main
KTestify is licensed under the Apache License 2.0.
