Table of Contents
Start your free trial.
Start your free trial.
Start your free trial.




Table of Contents
Executive Summary
This is part 2 of our contract testing series. Part 1 covered what a contract is, the three main approaches and when contract testing pays off. This post puts the schema-driven approach to work inside Kubernetes. Each change runs against mocks built from its neighbors' contracts: Prism for REST calls, Microcks for Kafka events, and one Testkube quality gate that gives the pipeline a single verdict.
From Contracts to Checks
In the schema-driven approach, the provider keeps its contract as an API description, and checks confirm that the real service matches it. Here the descriptions are OpenAPI files for the REST APIs and an AsyncAPI file for the event, kept in Git next to the code.
The same files also generate the mocks, a practice often called service virtualization. A mock built from a contract does more than answer: it can reject a request that breaks the contract, or check an event against its schema, so the break shows up on the branch that caused it. Because the mock comes from the file, it changes when the contract does, so it does not go stale the way a hand-written mock can.
The hard part has been where to run it. The mocks, a message broker and the service under test must start together, find each other and go away after the run, on every change. A Testkube Test Workflow does exactly that inside your own Kubernetes cluster.
The Example: One Service, Two APIs and One Event
The sample app is an order pipeline on Kubernetes, in order-demo-enterprise. This post tests order-service, which depends on three neighbors:
In part 1's terms, order-service is a consumer of auth-service and product-catalog, and the provider of the order-placed event that inventory-service consumes. So the checks cover both directions: the requests order-service sends, and the event it publishes.
The contract files were written from the app's existing Pact files, so every field comes from a real interaction. Each file carries named examples, because Microcks builds its mock responses from them.
Two tools share the work. Prism turns an OpenAPI file into a mock server that validates every request it receives. Microcks, a CNCF incubating project, mocks REST APIs from OpenAPI and also tests events on Kafka against an AsyncAPI file.
One Run at a Glance

The CI pipeline starts the gate for a branch, and the gate runs both workflows at once. Each workflow starts its own Kafka broker and runs order-service from that branch against its mocks. The branch passes only when both workflows pass, and a failed gate starts the AI agent.
Prism Mocks as Test Workflow Services
In Testkube, each mock is a service of the Test Workflow. A service runs in its own pod, is ready before the test step starts, and is gone when the run ends. The Prism workflow starts three of them: a Kafka broker and one Prism mock per REST dependency. Its test step starts order-service from the branch, places an order with a sku, and expects a 201 and the order-placed event.
The run in the dashboard lists the three services under Start services, before the test step:

The workflow, trimmed (the full file is in the repo):
kind: TestWorkflow
apiVersion: testworkflows.testkube.io/v1
metadata:
name: mock-services-prism
spec:
config:
branch:
type: string
default: mock-services-demo
content:
git:
uri: https://github.com/neuralnimbus22/order-demo-enterprise
revision: '{{config.branch}}'
container:
workingDir: /data/repo
services:
kafka:
image: apache/kafka:3.7.0
# single-node KRaft settings trimmed
shell: KAFKA_ADVERTISED_LISTENERS="PLAINTEXT://$(hostname -i):9092" exec /etc/kafka/docker/run
readinessProbe:
tcpSocket:
port: 9092
authmock:
image: stoplight/prism:5.15.10
content:
git:
uri: https://github.com/neuralnimbus22/order-demo-enterprise
revision: '{{config.branch}}'
paths:
- contracts
args: [mock, -m, "false", -h, 0.0.0.0, -p, "4010", /data/repo/contracts/auth-service.openapi.yaml]
logs: always
readinessProbe:
tcpSocket:
port: 4010
# catalogmock: the same, started with contracts/product-catalog.openapi.yaml
steps:
- name: Run order-service against Prism mocks and per-run Kafka
container:
image: node:20
run:
env:
- name: AUTH_URL
value: http://{{services.authmock.0.ip}}:4010
- name: CATALOG_URL
value: http://{{services.catalogmock.0.ip}}:4010
- name: KAFKA_BROKERS
value: '{{services.kafka.0.ip}}:9092'
shell: |
node services/order/server.js &
node tests/mock-services/prism-check.js
artifacts:
paths:
- target/mock-services/*.xmlWhat each part does:
content.gitchecks out the branch under test, so order-service and its contracts come from the same commit (content from Git).- Each mock is the stock Prism image started with one contract file.
-h 0.0.0.0makes it listen beyond localhost. With version 5.15.10, Prism also needed-m false(one process) to start in a container. - The test step reaches each service at
{{services.<name>.0.ip}}. The service names have no dashes, because a dash inside that expression reads as a minus sign. - Clients reach Kafka by pod IP, so the broker advertises its own IP when it starts.
logs: alwayskeeps each mock's log with the run. That log is where Prism writes the request that broke its contract.- The check writes a JUnit report, which Testkube keeps as an artifact of the run.
Microcks for the Kafka Event
Prism speaks only HTTP, so it never sees the event that order-service publishes. The Microcks workflow covers it in one test step: it imports the three contracts into Microcks, places an order through order-service while Microcks serves the REST mocks, and then runs a Microcks contract test on the order-placed topic.
That event test is a Microcks test of type ASYNC_API_SCHEMA, which consumes the messages on a topic and checks each one against the AsyncAPI schema (test parameters). Its endpoint points at the broker of the current run, and the code asks Microcks to read the topic from its first offset:
const endpoint = `kafka://${KAFKA_BROKERS[0]}/${KAFKA_TOPIC}?startOffset=0`;
const spec = JSON.stringify({ serviceId: 'order-placed:1.0.0', testEndpoint: endpoint,
runnerType: 'ASYNC_API_SCHEMA', timeout: 10000 });
const res = await fetch(`${MICROCKS_URL}/api/tests`, { method: 'POST',
headers: { 'Content-Type': 'application/json' }, body: spec });Kafka is still a service of each run. Microcks itself is installed once in the cluster, in its own namespace, with its async minion beside it. Each of the two needs the other's address when it starts, while a Test Workflow service gets its IP only after it starts, so a shared install with stable DNS names breaks that loop. The minion takes the broker from the test endpoint, so one Microcks serves every run, and the workflow receives its address as a configuration value.
One Gate, One Verdict
Neither workflow catches every break, so a third workflow runs both for the same branch. It uses execute to start the two workflows in parallel and fails when either one fails (running workflows from a workflow):
kind: TestWorkflow
apiVersion: testworkflows.testkube.io/v1
metadata:
name: mock-services-gate
labels:
mocks: gate
spec:
config:
branch:
type: string
default: mock-services-demo
steps:
- name: Run the Prism and Microcks workflows for the branch
execute:
parallelism: 2
workflows:
- name: mock-services-prism
config:
branch: '{{config.branch}}'
- name: mock-services-microcks
config:
branch: '{{config.branch}}'In the dashboard, the gate run lists its two child runs, and it fails when one of them fails:

The pipeline keeps what it does today and adds one step. Jenkins runs the Testkube CLI in a pod, sets its context with an API token stored as a Jenkins credential, and starts the gate for the branch (Jenkins):
sh 'testkube set context --api-key "$TK_API_TOKEN" --org-id "$TK_ORG" --env-id "$TK_ENV"'
sh 'testkube run testworkflow mock-services-gate --config branch="$BRANCH" -f'The end of the Jenkins console for the gate run above:

With -f, the CLI follows the run and exits with code 1 when the run fails, so the Jenkins build fails with it. One tip if your Jenkins agents run on Kubernetes: the kubeshop/testkube-cli image runs as user 1001, while the agent creates the workspace as user 1000. Run the CLI container as user 1000 and point HOME at the agent's work directory, or the shell steps never start.
Jenkins is one way in. The same gate can start from Bitbucket Pipelines or any other CI, on a schedule, from Kubernetes or Git events, through the CLI or the REST API, or from another workflow (ways to trigger a workflow). Each workflow's CI/CD Integration tab in the dashboard shows the snippet for each CI tool.
What Each Tool Caught
Each tool caught the change it validates, and the gate caught both. The gate ran on three branches of order-service; each break branch changes one line.
Each gate run took about 30 seconds, with both checks running in parallel.
On the event change, the Microcks test named the contract and the missing field:
order-placed contract (order-placed:1.0.0) violated
SUBSCRIBE order-placed: required property 'id' not foundIn the dashboard, the step log of the Microcks run shows it after the two checks that passed:

On the request change, order-service answered with a generic 502, by design: it never names its upstream services. The evidence sits in the authmock log of the same run, where Prism rejected the request:
[VALIDATOR] ✖ error Request body must have required property 'orderId'
Request terminated with error: ...UNPROCESSABLE_ENTITY: Invalid requestIn the dashboard, it is in the authmock service log of the failed Prism run:

Neither tool caught both changes. Prism never sees Kafka events, and in this setup the Microcks REST mock answered with its example response without checking the request body. That is why the gate runs both.
The event change is the rename from part 1, on an event: the provider renamed a field its consumer reads, and the gate failed before the change could ship. Both checks catch code that drifts from its contract. A change to a contract file itself needs its own check against the previous version, such as oasdiff for OpenAPI files.
Let AI Explain the Failure
A red gate tells the team that a contract broke; a Testkube AI agent tells them which one. The agent's instructions are short: how the two checks work, where their evidence lives, and the shape of the answer. An AI agent trigger starts it whenever a workflow labeled mocks: gate finishes with a failed status, so nobody has to ask.
On the request change, the agent read the gate run, found the failed Prism check, and read its JUnit report and the authmock log. Its answer named:
- Contract:
auth-service.openapi.yaml, operationPOST /authorize - Field:
orderId, required in the request body and missing from the request - Service: order-service
- Suggested fix: send
orderIdin the body of the call toPOST /authorize
The answer as it appears in the AI Chats tab of the failed run:

The agent can use your own models: OpenAI, Azure OpenAI or any OpenAI-compatible service. It works within the permissions of the user, and tools that change something can require approval before they run (AI architecture).
Conclusion
Each branch of order-service was checked against its neighbors' contracts inside the cluster, and one gate gave the pipeline a single verdict. The request change failed in Prism, the event change failed in Microcks, and the gate failed both branches. A rename like the one in part 1 now fails on the branch that made it, not in production.
The Prism mocks and the Kafka brokers are Test Workflow services, so they start with each run and are gone when it ends. Any mock tool that runs in a container fits the same way, such as WireMock or MockServer. When the gate fails, an AI agent explains which contract broke and how to fix it.
Part 1 covers when contract testing pays off and how to roll it out across teams. Part 3 covers provider-side checks with oasdiff.
To run checks like these in your own cluster, start with Test Workflow services in the Testkube docs, or talk to the Testkube team about your setup.
Sources
- Test Workflow services, Testkube docs
- Test Workflows: content, Testkube docs
- Advanced workflow orchestration (execute), Testkube docs
- Triggering Test Workflows, Testkube docs
- Jenkins, Testkube docs
- AI agents, Testkube docs
- AI agent triggers, Testkube docs
- AI architecture, Testkube docs
- testkube run testworkflow and its exit code with -f, Testkube CLI source
- Prism, Stoplight
- Microcks test parameters, Microcks docs
- Microcks becomes a CNCF incubating project, CNCF, May 7, 2026
- order-demo-enterprise, branch mock-services-demo, the sample app
Frequently Asked Questions
{{banner-get-started}}
About Testkube
Testkube is the open testing platform for AI-driven engineering teams. It runs tests directly in your Kubernetes clusters, works with any CI/CD system, and supports every testing tool your team uses. By removing CI/CD bottlenecks, Testkube helps teams ship faster with confidence.
Get Started with a trial to see Testkube in action.





