Q18 of 40 · Karate

How would you mock a third-party dependency in a Karate test?

KarateMidkaratemockingmock-serverdependency-injectionapi-testing

Short answer

Short answer: Start a Karate Mock Server on a known port before the suite, configure your service under test to call that port via karate-config.js or a system property, and define stub scenarios in the mock feature using pathMatches and methodIs predicates. Stop the server in an @AfterAll hook.

Detail

The flow is: test starts mock → configures service to call mock URL → test runs → mock intercepts service's outbound calls → test verifies the service's response.

Step 1 — write the mock feature:

Feature: Email service mock

  Scenario: pathMatches('/email/send') && methodIs('post')
    * def responseStatus = 202
    * def response = { messageId: 'mock-msg-1', queued: true }

  Scenario: pathMatches('/email/send') && methodIs('post')
              && request.to contains 'blocked@'
    * def responseStatus = 422
    * def response = { error: 'recipient blocked' }

Step 2 — start it in @BeforeAll:

emailMock = Karate.startMock("classpath:mocks/email-service.feature").port(8888);
System.setProperty("email.service.url", "http://localhost:8888");

Step 3 — karate-config.js picks it up:

var emailUrl = karate.properties['email.service.url'] || java.lang.System.getenv('EMAIL_SERVICE_URL');
return { emailServiceUrl: emailUrl };

Conditional stub responses: Karate Mock's scenario matching is evaluated top-to-bottom — add conditions to the Scenario line to return different responses for different request payloads (e.g., blocked recipients return 422).

// EXAMPLE

mocks/sms-gateway.feature

Feature: SMS gateway mock

  # Happy path — all phone numbers except +1999
  Scenario: pathMatches('/sms/send') && methodIs('post') && request.phone != '+19995551234'
    * def responseStatus = 200
    * def response = { messageId: '#(karate.uuid())', status: 'QUEUED' }

  # Simulated carrier rejection for specific number
  Scenario: pathMatches('/sms/send') && methodIs('post') && request.phone == '+19995551234'
    * def responseStatus = 422
    * def response = { error: 'CARRIER_REJECTED', phone: '#(request.phone)' }

  # Catch-all for unexpected paths
  Scenario:
    * def responseStatus = 404
    * def response = { error: 'not found in mock' }

// WHAT INTERVIEWERS LOOK FOR

The three-step pattern (write mock → start it → configure service to call it), conditional scenario matching in the mock feature, and stopping the server in teardown. Simulating error paths that the real service won't return on demand is the key value.

// COMMON PITFALL

Starting the mock but forgetting to configure the service under test to call the mock URL — the service continues calling the real external dependency, and the mock receives no traffic.