Q35 of 40 · Karate

How do you handle backwards compatibility testing with Karate (e.g. v1 vs v2 API)?

KarateSeniorkarateversioningbackwards-compatibilityapi-testingcontract-testing

Short answer

Short answer: Organise v1 and v2 tests in separate folders, set baseUrlV1 and baseUrlV2 in karate-config.js. Run both suites in CI against the live API. Tag v1 scenarios with @v1-deprecated and exclude them gradually as client migration completes. The v1 suite acts as a regression guard during the deprecation window.

Detail

Backwards compatibility testing means the v1 API continues to work correctly while v2 is being developed.

Folder structure:

features/
  users/
    v1/
      get-user.feature          @tag: v1
      create-user.feature       @tag: v1
    v2/
      get-user.feature          @tag: v2
      create-user.feature       @tag: v2

karate-config.js routes per version:

config.userServiceV1 = 'https://api.example.com/v1';
config.userServiceV2 = 'https://api.example.com/v2';

CI strategy:

  • Every PR: run @v2 tests (new contract, fast feedback)
  • Every PR: run @v1 tests (backwards compat guard)
  • v1 tests must stay green until the v1 sunset date

Deprecation tracking: tag v1 tests with @v1-deprecated-2026-Q2 — the date is visible in the feature file and in reports. Link to a Jira ticket in the feature description.

Additive changes (new optional field): add assertions only in v2 tests; v1 tests don't assert on the new field (they use match contains rather than match == if the field may or may not be present).

Breaking changes (field removal, type change): v1 tests fail → this is intentional — it tells you the v1 contract is broken and clients must migrate before the change ships.

// EXAMPLE

v1/get-user.feature

@v1 @v1-deprecated-2026-Q2
Feature: Get User — v1 contract (maintained until 2026 Q2)

  Background:
    * url userServiceV1
    * header Authorization = 'Bearer ' + bearerToken

  # v1 contract: response has {id, name, email} — no fullName, no createdAt
  Scenario: Get user returns v1 contract
    Given path '/users/1'
    When  method GET
    Then  status 200
    # Exact match — any extra field added to v1 response will fail this test
    # (catching unintentional v1 contract changes)
    And   match response == {
            id:    '#number',
            name:  '#string',
            email: '#string'
          }

# features/users/v2/get-user.feature
# @v2
# * match response == {
#     id:        '#number',
#     name:      '#string',
#     fullName:  '#string',   ← new in v2
#     email:     '#string',
#     createdAt: '#string'new in v2
#   }

// WHAT INTERVIEWERS LOOK FOR

Versioned folder organisation, separate URL config per version, CI running both v1 and v2 suites, and tagging v1 tests with deprecation dates. The strict v1 schema match as a contract regression guard is a sharp architectural detail.

// COMMON PITFALL

Using match contains for v1 tests to tolerate extra fields — this means adding a field to v1 doesn't fail the test, defeating the purpose of the v1 guard. Use strict match == for v1 to catch any unintended v1 contract changes.