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

  1. Only call karate.proceed() inside a scenario that is executed by a MockServer (the feature passed to karate.start({...mock:...}) or MockServer.feature(...))
  2. 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
  3. If the feature doubles as test and mock, guard the call: if (karate.proceed != null) { ... } or restructure into two features
  4. 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

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


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)