Testing Kafka with Karate — produce and consume, JSON and Avro, message headers, and a filtering
consumer. The broker and a Confluent Schema Registry come up with docker compose, so the example runs
standalone.
docker-compose.yml a single-process KRaft broker + Schema Registry
hello.avsc the Avro schema used by the Avro check
checks/kafka.feature the checks
karate-boot.js cluster coordinates for the whole suite, set once
- The engine —
karate-async-2.1.3.RC1.jarfrom the releases. Put it beside this folder. - A license — a
karate.licfile at.karate/karate.licin this folder, or the same text in theKARATE_LICENSE_TEXTenvironment variable. Kafka needs thekafkaentitlement. - JDK 21+ and Docker.
docker compose up -d
# wait for the registry, then create the two topics
curl -sf localhost:8081/subjects
for t in json-topic avro-topic; do
docker compose exec -T kafka kafka-topics --create --if-not-exists \
--topic "$t" --partitions 1 --replication-factor 1 --bootstrap-server localhost:29092
done
java -jar ../karate-async-2.1.3.RC1.jar checks
docker compose downFour scenarios should pass. The HTML report is written to target/karate-reports/.
See it without running anything. This kit runs on every push, and its report is published here: https://karatelabs.github.io/karate-agent-examples/kafka/
Open a channel, then take a producer and a consumer from it:
* def channel = karate.channel('kafka')
* def consumer = channel.consumer()
* consumer.topic = 'json-topic'
* consumer.count = 1
* consumer.start()
* def producer = channel.producer()
* producer.topic = 'json-topic'
* producer.key = 'k1'
* producer.value = { message: 'hello', n: 42 }
* producer.send()
* match consumer.pop().value == { message: 'hello', n: 42 }Start the consumer before you produce. start() subscribes and returns immediately; if you produce
first, the message can be gone before anyone is listening. pop() reads one record and collect() reads
count of them — those are the calls that block, bounded by timeout.
Each record that comes back is { key, value, headers, offset }.
Producer keys: topic · key · value · headers · schema · send()
Consumer keys: topic · count · timeout · filter · schema · start() · pop() · collect()
filter takes a function; only the records it accepts are counted. This is useful when a topic
carries traffic your check does not care about:
* consumer.filter = x => x.key != 'skip-me'Register a schema once by name, then refer to it from the producer and the consumer:
* channel.register({ name: 'hello', path: '/hello.avsc' })
* producer.schema = 'hello'
* consumer.schema = 'hello'.avsc is Avro and goes through the Schema Registry, so kafka.schemaRegistry must be set in
karate-boot.js. .proto is Protobuf, sent as bytes and decoded with the registered descriptor — no
registry needed:
* channel.register({ name: 'hello-proto', path: '/hello.proto', message: 'Hello', roots: ['/'] })With no schema at all, values are plain JSON bytes — nothing to register.
A path is resolved against the project — this folder. /hello.avsc is anchored at the project root,
hello.avsc is relative to it, and both find the same file. For a file outside the project — a
keystore mounted as a secret — write file:/path/to/keystore.p12. A leading / always means the
project, never the file system root.
Set the keystore and truststore in karate-boot.js:
var kafka = boot.ext('kafka');
kafka.ssl = {
protocol: 'SSL',
truststore: '/ssl/client.truststore.p12', truststorePassword: 'secret',
keystore: '/ssl/client.keystore.p12', keystorePassword: 'secret', keyPassword: 'secret'
};
// PKCS12 keystores need this — the Kafka client assumes JKS otherwise
kafka.props = { 'ssl.truststore.type': 'PKCS12', 'ssl.keystore.type': 'PKCS12' };For SASL, put the usual sasl.* client properties in kafka.props.
Set kafka.bootstrap in karate-boot.js — and kafka.schemaRegistry too, if you use Avro. Put your
own topic names in the checks. Then delete docker-compose.yml; it exists only so this example runs
standalone. Both values read an override at run time, so CI can point elsewhere without editing a file:
java -Dkafka.bootstrap=broker.internal:9092 -jar ../karate-async-2.1.3.RC1.jar checksThe -D must come before -jar — that is where the JVM reads it. After the jar name it would be
read as a CLI flag instead.