Core Concepts
ConsumedRecord<V>โ
The only data type that crosses layer boundaries. It is the universal output of the transport layer (RecordFetcher<V> for asynchronous transports, RequestResponseClient<Req, V> for synchronous ones since 1.1.1) and the universal input of the assertion layer.
@Value
public class ConsumedRecord<V> {
String source; // topic name, container name, request URL, ...
int partition;
long offset;
String key;
V value; // String for raw, GenericRecord for Avro
Instant timestamp;
Map<String, String> headers; // protocol headers (Kafka headers, HTTP response headers, ...)
Map<String, String> attributes; // NEW in 1.1.1, transport metadata, never null, defaults to emptyMap()
static <V> ConsumedRecord<V> fromKafkaRecord(ConsumerRecord<String, V> record) { ... }
MatchedRecord toMatchedRecord() { ... }
}
attributes holds structured transport metadata that does not belong under headers, for example an HTTP status code and elapsed time, a future gRPC status code, or an MQ reason code. Kafka and Azure Blob leave it empty. A synchronous transport plugin populates it, and AttributeRecordMatcher (see Built-in matchers โ) asserts against it.
ConsumedRecord ships both a full constructor (accepting attributes) and a backward-compatible overload without it (defaults to Collections.emptyMap()), plus a @Builder, so existing transports keep compiling unchanged.
MatchedRecordโ
Deduplication token, represents a record that has already been claimed by a consumer step. It holds topic + partition + offset + key + timestamp and is stored in the static deduplication registry.
MatchedRecord deliberately excludes processedTime from equals/hashCode so that two records from the same Kafka partition+offset are always considered the same, regardless of when they were processed.
MatchContextโ
Immutable context object passed to a RecordMatcher. Built by AbstractKafkaConsumer.buildMatchContext() (or AbstractSynchronousConsumer.buildMatchContext() for synchronous transports) from the ConsumerContext.
@Value @Builder
public class MatchContext {
String matchMethod;
List<String> matchFilePaths; // replaces old matchFilePath (String)
List<String> excludedFields; // defaults to emptyList()
boolean strictMatching;
String matchKey;
String matchValue;
Map<String,String> expectedAttributes; // NEW in 1.1.1, defaults to emptyMap(), used by AttributeRecordMatcher
// Convenience, for single-record matchers
public String getMatchFilePath() {
return matchFilePaths != null && !matchFilePaths.isEmpty()
? matchFilePaths.get(0) : null;
}
}
expectedAttributes holds the key/value pairs to assert against a record's attributes map, for example {"statusCode": "200"}. It follows the same convention as excludedFields, always non-null, defaults to an empty map, and matchers check isEmpty() rather than null.
MatchResultโ
Returned by every RecordMatcher. Carries a pass/fail flag plus the diff, expected, and actual strings for failure reporting.
@Value
public class MatchResult {
boolean passed;
String diff;
String expected;
String actual;
static MatchResult pass() { ... }
static MatchResult fail(String diff, String expected, String actual) { ... }
}
ConsumerContext<K, V>โ
Immutable builder that configures a consumer operation. Built by ConsumerValidationService in ktestify-cucumber from DataTable values.
Key fields:
| Field | Type | Description |
|---|---|---|
topic | Topic | The output topic (must be OUTPUT type โ validated in builder) |
matchMethod | String | One of the ConfigConstants.method* values |
matchFilePaths | List<String> | Expected file paths (single or batch) |
excludedFields | List<String> | Field names to ignore in comparison |
expectedRecordKey | String | Key filter โ record must match this key |
readTimeout | long | Milliseconds |
consumerDeltaTime | long | Milliseconds (DataTable seconds ร 1000) |
isBatchConsumer | boolean | Enables batch fetch mode |
batchSize | int | Number of records to collect in batch mode |
Topicโ
@Data @Builder
public class Topic {
String topicName;
String topicAlias;
String topicNamespace;
Topic.Type topicType; // INPUT or OUTPUT
// Returns "namespace.topicName" or just "topicName" if no namespace
String getNamespacedTopic() { ... }
}
RecordMatcherFactoryโ
Pure static factory, no DI, no singleton. Resolves the right RecordMatcher implementation based on matchMethod and whether the consumer is raw or Avro.
RecordMatcherFactory.forRaw("matchFile") โ FileRecordMatcher
RecordMatcherFactory.forAvro("matchFile") โ AvroFileRecordMatcher
RecordMatcherFactory.forRaw("matchXML") โ XmlRecordMatcher
RecordMatcherFactory.forAvro("matchXML") โ throws ConsumerException โ not supported
RecordMatcherFactory.forRaw("methodMatchAttributes") โ AttributeRecordMatcher<>() // NEW in 1.1.1, raw only
See Built-in matchers โ for the full mapping table.
Synchronous transports (1.1.1)โ
Alongside RecordFetcher<V> (async, poll until a record appears), ktestify-core now ships a sibling contract for synchronous, caller-initiated transports:
public interface RequestResponseClient<Req, V> extends AutoCloseable {
List<ConsumedRecord<V>> execute(Req request) throws FetchException;
void close();
}
Its orchestration counterpart, AbstractSynchronousConsumer<Req, V>, mirrors AbstractKafkaConsumer (build a request, call execute, hand the result to a RecordMatcher, return MatchResult.isPassed()), and a generic decorator, PollingRequestResponseClient<Req, V>, adds retry-until-predicate-or-timeout semantics that any synchronous transport plugin can reuse instead of writing its own poll loop.
Full details, including the client lifecycle and how to implement a new synchronous transport, live on the Synchronous transports โ page.
Dynamic variable systemโ
All file reads go through FileUtils.getFileContent(path), which transparently calls DynamicVariableProcessor.process(content) before returning. To add a new variable type:
- Implement
DynamicVariableinterface. - Register it in
DynamicVariableFactory. - No changes needed to
FileUtilsorDynamicVariableProcessor.
See Dynamic variables โ for the full list of built-in types.