Mock an API Programmatically¶
Use the mock server library directly when you need programmatic control over the mock server. This is the right choice for custom test harnesses, non-Spring frameworks, or standalone usage.
If you use Spring Boot, consider Mock Server (Spring Boot) for an annotation-based setup.
The contracteer-examples repository contains complete working projects.
Prerequisites¶
- JDK 21 or later
- Gradle or Maven
- An OpenAPI 3.0 specification (
.yamlor.json)
Add the Dependency¶
Start the Mock Server¶
Three steps: load the specification, create the mock server, start it.
// 1. Load the OpenAPI specification
val result = OpenApiLoader.loadOperations("classpath:openapi.yaml")
if (result.isFailure()) {
fail("Failed to load spec: ${result.errors()}")
}
// 2. Create the mock server
val mockServer = MockServer(
operations = result.value!!,
port = 0 // 0 for random port, or a fixed port
)
// 3. Start and use
mockServer.start()
val baseUrl = "http://localhost:${mockServer.port()}"
// Make HTTP requests to baseUrl...
mockServer.stop()
// 1. Load the OpenAPI specification
var result = OpenApiLoader.loadOperations("classpath:openapi.yaml");
if (result.isFailure()) {
fail("Failed to load spec: " + result.errors());
}
// 2. Create the mock server
var mockServer = new MockServer(result.getValue(), 0);
// 3. Start and use
mockServer.start();
var baseUrl = "http://localhost:" + mockServer.port();
// Make HTTP requests to baseUrl...
mockServer.stop();
OpenApiLoader.loadOperations() accepts a file path, an HTTP(S) URL, or a classpath resource (e.g., classpath:openapi.yaml).
Set port to 0 for a random available port, or to a fixed value if your test setup requires it.
Call mockServer.port() after start() to get the actual port.
Treat the specification as a shared artifact
Contracteer encourages specification-driven contract testing: the OpenAPI specification exists independently of both server and client.
Package it as a Maven or Gradle dependency and reference it with classpath:openapi.yaml.
This ensures that the server, client, and contract tests all use the same specification.
The contracteer-examples repository demonstrates this pattern with the musketeer-spec module.
How the Mock Server Responds¶
The mock server is not a hand-written stub. It validates every incoming request against the OpenAPI schema and determines the response from the specification.
Request validation¶
The mock server validates request parameters and body against the schema.
The client sends {name: "d'Artagnan", rank: "KNIGHT", weapon: "Rapier"} to POST /musketeers.
The mock server checks the rank field against the schema:
KNIGHT is not in the enum.
The operation defines a 400 response, so the mock server returns 400.
No one wrote a mock rule for this -- the schema drives the rejection.
Scenario matching¶
If the request is valid, the mock server compares it against the scenarios defined in the specification.
The client sends {name: "d'Artagnan", rank: "CADET", weapon: "Rapier"} to POST /musketeers.
This matches the D_ARTAGNAN_JOINS scenario:
post:
requestBody:
content:
application/json:
examples:
D_ARTAGNAN_JOINS:
value:
name: d'Artagnan
rank: CADET
weapon: Rapier
responses:
'201':
headers:
Location:
examples:
D_ARTAGNAN_JOINS:
value: /musketeers/4
The mock server returns 201 with Location: /musketeers/4.
Status-code-prefixed scenarios¶
The client calls GET /musketeers/999.
The request is valid (999 is an integer), and the value matches the 404_UNKNOWN_MUSKETEER scenario:
The key's prefix (404_) targets the 404 response directly.
The mock server returns 404.
Schema-only response¶
If the request is valid but matches no scenario, the mock server generates a response from the schema.
GET /musketeers has no examples in the specification.
The mock server returns 200 with an array of randomly generated Musketeer objects.
The values satisfy the schema but are different on each run.
The 418 diagnostic¶
If the mock server cannot determine the correct response, it returns 418 with diagnostic information.
The 418 is not a status code from your API -- it is Contracteer telling you that something is ambiguous or undefined.
This happens when multiple scenarios match the same request.
It also occurs when multiple 2xx response codes exist without a scenario to disambiguate.
An invalid request with no 400, 4XX, or default response defined also triggers a 418.
The 418 body explains what went wrong. Read it before investigating further -- it usually points directly to the cause.
Assert Structure, Not Values¶
Contract tests verify that your client handles the documented response structure -- not that the server returns specific data. Assert that fields are present and correctly typed. Do not assert on example values from the specification.
A test that asserts a response field equals a specific example value is a functional test, not a contract test. Your client must handle any valid response, not just the example data.
See Assert Structure, Not Values for the full rationale.
Debugging¶
When the mock server returns a 418 diagnostic response, Contracteer logs the request at WARN level automatically.
No configuration is needed.
To see all incoming requests and outgoing responses, set the tech.sabai.contracteer.http logger to DEBUG.
Next Steps¶
- Testing Your Client -- how the mock server validates requests and generates responses in depth.
- Creating Scenarios -- how to write OpenAPI examples that produce the scenarios you want.
- Mock Server (Spring Boot) -- annotation-based setup for Spring Boot.
- contracteer-examples -- complete working projects with server and client examples.