Skip to main content

Adding a New Transport

New transports in KTestify are plugins, self-contained Maven modules that hook into the framework at runtime without touching any existing code. This is the same model used by ktestify-plugin-azureblob.

Before reading this page, make sure you understand the Plugin System →.


Why the plugin model​

KTestify's transport layer is fully abstracted behind RecordFetcher<V> and ConsumedRecord<V>. All existing matchers (FileRecordMatcher, XmlRecordMatcher, AvroFileRecordMatcher, …) work on List<ConsumedRecord<V>> regardless of where those records came from. A new transport only needs to produce ConsumedRecord objects, everything else is already done.

The plugin system lets you ship that new transport as a separate JAR dropped into /workspace/plugins at runtime, no rebuild of ktestify-core or ktestify-cucumber required.


Step 1 - Create a Maven module​

ktestify-plugin-ibmmq/
├── pom.xml
└── src/main/java/io/github/ktestify/plugin/ibmmq/
├── IbmMqPlugin.java ← implements KtestifyPlugin
├── IbmMqRecordFetcher.java ← implements RecordFetcher<V>
├── IbmMqConsumerContext.java
└── steps/
└── IbmMqStepDefinitions.java ← Cucumber step definitions

Your pom.xml declares ktestify-core as its only KTestify dependency:

<dependency>
<groupId>io.github.ktestify</groupId>
<artifactId>ktestify-core</artifactId>
<version>${ktestify.version}</version>
</dependency>

Step 2 - Implement RecordFetcher<V>​

This is the transport contract. Wrap each incoming message as a ConsumedRecord<V> and throw FetchException on timeout or broker error:

public class IbmMqRecordFetcher<V> implements RecordFetcher<V> {

private final IbmMqConsumerContext context;

@Override
public List<ConsumedRecord<V>> fetch() throws FetchException {
try {
Message msg = consumer.receive(context.getReadTimeoutMs());
if (msg == null) {
throw new FetchException("Timed out waiting for a message on queue '"
+ context.getQueueName() + "'");
}
return List.of(ConsumedRecord.<V>builder()
.source(context.getQueueName())
.partition(0)
.offset(0)
.key(msg.getJMSCorrelationID())
.value((V) msg.getBody(String.class))
.timestamp(msg.getJMSTimestamp())
.build());
} catch (JMSException e) {
throw new FetchException("IBM MQ error: " + e.getMessage(), e);
}
}

@Override
public void close() { /* close JMS session/connection */ }
}

Step 3 - Implement KtestifyPlugin​

Register your fetcher and any Cucumber step definitions through the plugin interface:

public class IbmMqPlugin implements KtestifyPlugin {

@Override
public String getPluginId() { return "ibmmq"; }

@Override
public void initialize(PluginContext context) {
// read connection details from HOCON config
String host = context.getConfig().getConfig().getString("ktestify.ibmmq.host");
int port = context.getConfig().getConfig().getInt("ktestify.ibmmq.port");
String manager = context.getConfig().getConfig().getString("ktestify.ibmmq.queue-manager");
// set up JMS connection factory, store on the plugin instance
}
}

Register it in a Cucumber @Before hook inside your step definitions class:

@Before(order = 0)
public void registerPlugin() {
PluginRegistry.register(new IbmMqPlugin());
}

Step 4 - Add Gherkin steps​

Create step definitions that retrieve your plugin from PluginRegistry and expose the Gherkin DSL:

@Given("output queue")
public void givenOutputQueue(DataTable table) {
// register queue in shared resources
}

@Then("expected queue record from file")
public void thenExpectedQueueRecord(DataTable table) {
IbmMqPlugin plugin = (IbmMqPlugin) PluginRegistry.get("ibmmq");
// build IbmMqConsumerContext from table
// run IbmMqRecordFetcher → matcher → assert
}

Resulting Gherkin:

Given output queue
| queueName | queueAlias |
| MY.OUT.QUEUE | orders-out |

Then expected queue record from file
| queueAlias | file |
| orders-out | expected.txt |

Step 5 - Plugin configuration​

By convention, read your settings from under the ktestify namespace:

application.conf
ktestify {
ibmmq {
host = "mq.internal"
port = 1414
queue-manager = "QM1"
channel = "DEV.APP.SVRCONN"
}
}

Step 6 - Drop the JAR into /workspace/plugins​

Build your plugin JAR and mount it into the container. KTestify scans /workspace/plugins at startup and loads any JARs it finds:

docker run --rm \
-v "$(pwd)/workspace/features:/workspace/features" \
-v "$(pwd)/plugins:/workspace/plugins" \
-e KTESTIFY_BOOTSTRAP_SERVERS=kafka:9092 \
ghcr.io/ktestify/ktestify-cucumber:latest \
/workspace/features

No rebuild of ktestify-cucumber required. The plugin is discovered and its step definitions are picked up automatically by Cucumber's classpath scanning.


See also​