karate-testing

Authors Karate `.feature` files using its Gherkin-flavored DSL for HTTP API tests, leverages the `match` keyword with fuzzy validators (#number / #string / #regex / contains / arrays), runs the suite

SKILL.md

GitHub

karate-testing

Overview

Karate authors HTTP API tests as Gherkin-flavored .feature files with Given / When / Then steps, but unlike Cucumber the steps are executable directly - no glue code is required (karate-readme, karate-docs).

This skill covers the API-testing slice of Karate. Its mock-server and UI-automation modes are out of scope here.

When to use

  • The team wants feature-file-based authoring (Gherkin shape) without step-definition glue code (the Cucumber pain point).
  • Multi-language test teams: Karate's DSL is approachable for non- Java developers (the .feature file IS the test; no Java to read).
  • API + mock + perf in one tool - Karate's distinguishing feature is cross-mode reuse of feature files.
  • Teams that need built-in JSON path + schema-fuzzy matching with one keyword (match).

If the team is already deep in REST Assured fluency, evaluate restassured-testing. For non-JVM stacks, use postman-collections or tavern-testing.

Install

Maven (consult karate-docs for the current version - Karate publishes regularly under group io.karatelabs, artifacts karate-core and karate-junit5):

<dependency>
    <groupId>io.karatelabs</groupId>
    <artifactId>karate-junit5</artifactId>
    <version>${karate.version}</version>
    <scope>test</scope>
</dependency>

Pin ${karate.version} to a specific release listed on the GitHub release page (karate-readme). Do not float on LATEST - DSL keywords have evolved across major versions.

Authoring

Feature file shape

Each .feature file lives under src/test/java/<package>/ (yes, under java/ even though it's not Java - Maven's resource conventions). Per karate-docs:

Feature: Simple API test example

  Scenario: Get user details
    Given url 'https://api.example.com'
    When method GET
    Then status 200
    And match response == { id: '#number', name: '#string', email: '#string', active: true }

The full step-keyword table (url, path, header, param, request, method, status, match) and its purpose column are in references/match-reference.md.

The match keyword

match is Karate's superpower - fuzzy structural matching on JSON or XML (karate-docs):

# Type fuzzy: any number, any string
And match response == { id: '#number', name: '#string' }

# Array contains
And match response.items contains { sku: 'SKU-123' }

# Optional / nullable fields
And match response == { id: '#number', deletedAt: '##string' }   # ## = optional

The full match-modifier vocabulary (#boolean, #array, #object, #null, #notnull, #present, #notpresent, #regex <pattern>, ##<type>) and more examples are in references/match-reference.md.

Background block

For shared setup across scenarios:

Feature: Orders API

  Background:
    Given url 'https://api.example.com'
    And header Authorization = 'Bearer ' + karate.properties['api.token']

  Scenario: Get an order
    Given path 'orders/42'
    When method GET
    Then status 200
    And match response.order_id == 42

  Scenario: Create an order
    Given path 'orders'
    And request { sku: 'SKU-1', qty: 2 }
    When method POST
    Then status 201
    And match response == { order_id: '#number', sku: 'SKU-1', qty: 2 }

karate.properties['api.token'] reads a system property (passed via mvn test -Dapi.token=...) - the canonical pattern for secret injection without hard-coding.

Variables and chaining

Scenario: Create then read
  Given path 'orders'
  And request { sku: 'SKU-1', qty: 2 }
  When method POST
  Then status 201
  * def newId = response.order_id

  Given path 'orders/' + newId
  When method GET
  Then status 200
  And match response.sku == 'SKU-1'

* def name = expr declares a variable usable in subsequent steps. * is a step prefix (synonym of Given/When/Then - Karate keywords are documentation only when not asserting).

Running via JUnit 5

Karate ships a JUnit 5 runner that picks up .feature files adjacent to a Java runner class. Convention:

package com.example.api;

import com.intuit.karate.junit5.Karate;

class OrdersApiRunner {

    @Karate.Test
    Karate orders() {
        return Karate.run("orders").relativeTo(getClass());
    }
}

Karate.run("orders") resolves to orders.feature next to the runner. For multi-feature suites, add more @Karate.Test methods or use Karate.run().relativeTo(getClass()) to pick up every .feature in the same directory.

Run via Maven:

mvn test -Dapi.token=$API_TOKEN

(Per karate-docs.)

Reporting

Karate emits Cucumber-style HTML reports under target/karate-reports/, plus JUnit XML under target/karate-reports/karate-summary.xml. The JUnit XML is canonical for CI ingestion (same shape as Newman / REST Assured / JUnit's own *Test.java output).

CI integration

# .github/workflows/karate.yml
name: api-tests

on:
  pull_request:
  push:
    branches: [main]

jobs:
  karate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5

      - uses: actions/setup-java@v4
        with:
          distribution: 'temurin'
          java-version: '21'
          cache: 'maven'

      - name: Run Karate suite
        env:
          API_TOKEN: ${{ secrets.STAGING_API_TOKEN }}
        run: mvn -B test -Dapi.token=$API_TOKEN

      - name: Upload Karate reports
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: karate-reports
          path: target/karate-reports/
          retention-days: 14

      - name: Surface JUnit results
        if: always()
        uses: dorny/test-reporter@v1
        with:
          name: Karate API tests
          path: target/karate-reports/karate-summary.xml
          reporter: java-junit

Anti-patterns

Anti-pattern Why it fails Fix
Hard-coded URLs and tokens in .feature files Tests bind to one environment; secrets leak. Use karate.properties['api.url'] and karate.properties['api.token'].
match response == ... with literal IDs from a non-deterministic source Test fails on every run because the ID changes. Use '#number' instead of a literal; OR capture the ID with * def id = response.id and assert on it explicitly.
Combining UI + API + mock in one Feature When one mode breaks, the unrelated assertions become noise. One mode per Feature; cross-link with * call read('helper.feature').
Missing Background for shared auth Every Scenario duplicates auth steps; rotation pain. Pull auth into Background.
Skipping --bail equivalent in CI A failed scenario doesn't stop subsequent scenarios; long failure logs. Karate doesn't bail mid-Feature by design; for fast-fail, run each Feature in its own JUnit class and use mvn -fae or -Dsurefire.failIfNoTests=false patterns.

Limitations

  • JVM only. Karate runs on the JVM; non-JVM teams should look at tavern-testing (Python) or postman-collections (Node).
  • DSL learning curve for non-Gherkin users. The match keyword's modifier vocabulary takes a session to internalize.
  • .feature files under src/test/java. Maven convention is surprising for first-timers; misplacing them under src/test/resources breaks the JUnit 5 runner's auto-discovery.
  • Cucumber-style reports are HTML-heavy. Combine with the karate-summary.xml for CI gating; the HTML is for human triage, not machine consumption.

References

  • karate-readme - main repo: positioning, dependency name, release cadence.
  • karate-docs - canonical DSL reference: feature-file syntax, match keyword, scenario / background / scenario outline, JUnit 5 runner pattern.
  • postman-collections - JSON-driven alternative.
  • restassured-testing - Java fluent-DSL alternative on the same JVM stack.