Writing a Custom Matcher
RecordMatcher<V> is a @FunctionalInterface, implementing one is straightforward. You can add custom comparison logic (schema-based validation, regex, binary format, etc.) without touching any existing code.
Implement RecordMatcher<V>โ
package io.github.ktestify.match.impl;
import io.github.ktestify.match.MatchContext;
import io.github.ktestify.match.MatchResult;
import io.github.ktestify.match.RecordMatcher;
import io.github.ktestify.models.ConsumedRecord;
import java.util.List;
public class RegexRecordMatcher implements RecordMatcher<String> {
@Override
public MatchResult match(List<ConsumedRecord<String>> records, MatchContext context) {
if (records.isEmpty()) {
return MatchResult.fail("No records received", context.getMatchValue(), "");
}
String value = records.get(0).getValue();
String pattern = context.getMatchValue(); // regex stored in matchValue
if (value.matches(pattern)) {
return MatchResult.pass();
}
return MatchResult.fail(
"Value did not match regex",
pattern,
value
);
}
}
Register in RecordMatcherFactoryโ
Add your method name constant to ConfigConstants:
// ConfigConstants.java
public static final String METHOD_MATCH_REGEX = "matchRegex";
Then add a branch in RecordMatcherFactory.forRaw():
// RecordMatcherFactory.java
public static RecordMatcher<String> forRaw(String matchMethod) {
return switch (matchMethod) {
// ...existing cases...
case ConfigConstants.METHOD_MATCH_REGEX -> new RegexRecordMatcher();
default -> new NoOpRecordMatcher<>();
};
}
Use it from a Gherkin stepโ
The simplest integration is to add a new Then step in ValidationStepDefinition that sets matchMethod = "matchRegex" and matchValue = pattern in the ConsumerContext:
@Then("expected record value matches regex")
public void validateRawRegex(DataTable table) {
Map<String, String> row = DataTableUtils.firstRow(table);
consumerValidationService.validateWithMatchValue(
row.get("topicAlias"),
"matchRegex",
row.get("pattern")
);
}
Gherkin usage:
Then expected record value matches regex
| topicAlias | pattern |
| orders-out | .*"status":"PROCESSED".* |
Design rules to followโ
- Never throw from
match(), returnMatchResult.fail(...)on assertion failure. Only throwComparisonExceptionfor configuration errors (missing file, bad XPath expression, etc.). - Always handle empty
recordslist, the consumer may legitimately receive 0 records if the watcher step lets it. - Use
context.getMatchFilePath()(convenience accessor) for single-record matchers; iteratecontext.getMatchFilePaths()for batch matchers. excludedFieldsdefaults toemptyList(), guard with!context.getExcludedFields().isEmpty()not!= null.