karatelabs/karate · error · RuntimeException
proceed() can only be called within a mock scenario
Error message
proceed() can only be called within a mock scenario
What it means
karate.proceed() forwards the request currently being handled by a mock server to a real target URL (proxy mode). It only makes sense while a mock scenario is executing a request, because Karate needs the in-flight HttpRequest. Karate throws this error when mockHandler is null — i.e. proceed() was called from a scenario/test where no mock server handler is active.
Solutions
- Only call karate.proceed() inside a scenario that is executed by a MockServer (the feature passed to karate.start({...mock:...}) or MockServer.feature(...))
- In a regular test, replace proceed() with a direct HTTP call: karate.call('other.feature') or use the http client to hit the real backend
- If the feature doubles as test and mock, guard the call: if (karate.proceed != null) { ... } or restructure into two features
- Verify you are not calling proceed() from setup/teardown hooks where no request is in flight
Example fix
// before (regular test feature)
When method get
Then karate.proceed() // fails: no mock handler active
// after (mock feature used via karate.start({mock: 'proxy.feature'}))
* def response = karate.proceed('http://backend:8080') Defensive patterns
Strategy: try-catch
Validate before calling
// Only call proceed in features launched via karate.start({mock: ...}) or MockServer.feature(...).
// Structure check: the file calling proceed() should be the mock feature, not a test feature.
// No pre-call API exists to check mockHandler; keep mock features separate from test features. Try / catch
try {
var response = karate.proceed('http://backend:8080');
} catch (e) {
if (('' + e).indexOf('can only be called within a mock scenario') !== -1) {
karate.log('proceed() called outside a mock handler — falling back to direct call');
}
throw e;
} Prevention
- Keep proxy/mock features (that call proceed()) strictly separate from regular test features
- Never call proceed() from background sections, callSingle, or hooks
- Document in the mock feature header that it only runs under MockServer
- If one feature serves both roles, branch on a flag set by the caller instead of assuming proceed() exists
When it happens
Trigger: Calling karate.proceed() (with or without a URL argument) inside: a normal API test scenario (not a mock), a karate.callSingle setup, an @beforeall/@afterall hook, or a mock feature that runs outside the MockServer request-handling path.
Common situations: Copy-pasting a proceed() snippet from a mock-proxy example into a regular test feature; calling proceed() in a background section of a regular feature; running the same feature both as a test and as a mock without guarding the call.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- start() argument must be a string path or config map
- proceed() needs a target URL or Host header in request
- read() needs at least one argument
- eval() needs one argument
- expect() needs at least one argument
AI-assisted analysis of karatelabs/karate@a22eb90246 (2026-09-12).
Data as JSON: /api/errors/53d39d4d63aaddd3.
Report an issue: GitHub.
Appendix: source
Thrown at karate-core/src/main/java/io/karatelabs/core/KarateJs.java:997
};
}
/**
* karate.proceed() - Forward the current request to a target URL (proxy mode).
* Can only be used within a mock scenario.
* Usage:
* <pre>
* // Forward to specific target
* var response = karate.proceed('http://backend:8080');
*
* // Forward using Host header from request
* var response = karate.proceed();
* </pre>
*/
private JavaInvokable proceed() {
return args -> {
if (mockHandler == null) {
throw new RuntimeException("proceed() can only be called within a mock scenario");
}
HttpRequest currentRequest = mockHandler.getCurrentRequest();
String targetUrl;
if (args.length > 0 && args[0] != null) {
targetUrl = args[0].toString();
} else {
// Use Host header from request
String host = currentRequest.getHeader("Host");
if (host == null) {
throw new RuntimeException("proceed() needs a target URL or Host header in request");
}
targetUrl = "http://" + host;
}
// Build request manually to avoid header conflicts
HttpRequestBuilder builder = new HttpRequestBuilder(client);
builder.url(targetUrl);View on GitHub (pinned to a22eb90246)