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 via JUnit 5 plus Maven Surefire, and produces JUnit XML for CI gating. Use when the project is on the JVM and prefers a feature-file authoring flow over Java-DSL fluent chains; for those fluent chains use restassured-testing, for the same YAML-style flow on a Python/pytest stack use tavern-testing.
Install with skills.sh (any agent)
npx skills add testland/qa --skill karate-testingkarate-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 (opens in new window), karate-docs (opens in new window)).
This skill covers the API-testing slice of Karate. Its mock-server and UI-automation modes are out of scope here.
When to use
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 (opens in new window) 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 (opens in new window)). 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 (opens in new window):
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 (opens in new window)):
# 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' } # ## = optionalThe 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 (opens in new window).)
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-junitAnti-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
References
Karate keyword and match-modifier reference
View source (opens in new window)Karate keyword and match-modifier reference
Companion to the karate-testing skill: the full step-keyword table and the complete match modifier vocabulary. Per karate-docs (opens in new window).
Step keywords
| Keyword | Purpose |
|---|---|
Feature: | One per file; describes the API surface under test. |
Scenario: | One per case; mirrors a single user / API journey. |
Background: | Steps that run before each Scenario (auth, base URL setup). |
Given url '...' | Set the request URL. |
Given path '...' | Append a path segment to the URL. |
Given header X = Y | Set a request header. |
Given param X = Y | Set a query parameter. |
Given request {...} | Set the JSON body. |
When method <verb> | Issue the HTTP request: get, post, put, delete. |
Then status N | Assert the response status code. |
And match expr | Assert response body / header / variable shape. |
Match examples
# Equality on the whole response
And match response == { id: 1, name: 'Alice' }
# Type fuzzy: any number, any string
And match response == { id: '#number', name: '#string' }
# Regex constraint
And match response.email == '#regex .*@example\\.com'
# Array contains
And match response.items contains { sku: 'SKU-123' }
# Array length / shape
And match response.items == '#array'
And match response.items[0] == { sku: '#string', qty: '#number' }
# Optional / nullable fields
And match response == { id: '#number', deletedAt: '##string' } # ## = optionalMatch modifiers
#number, #string, #boolean, #array, #object, #null, #notnull, #present, #notpresent, #regex <pattern>, and ##<type> for optional compose into a small but expressive matcher language (karate-docs (opens in new window)).
Related skills
api-chaos-runner
Runs the project's existing API tests under injected network chaos - latency, timeouts, dropped connections, bandwidth caps, packet loss - via Toxiproxy (notes on Pumba / Gremlin / LitmusChaos). Builds a per-scenario chaos matrix and reports which assertions break under which conditions, verifying resilience patterns (retry, circuit-breaker, timeout, fallback). Unlike schemathesis-fuzzing and restler-fuzzing, which generate new tests from a schema, this drives your EXISTING example-based suite.
postman-collections
Repairs Postman and Newman runs in CI - a reporter that never writes the HTML file the pipeline expects, a nightly job that fires requests as fast as it can until a partner API rate-limits it, or a report with one row per request when the collection asserts a dozen things between them. Authors Postman collections (requests, tests, variables, environments), runs them headless via the Newman CLI, configures reporters (cli / json / junit / html) for CI artifact upload, and drives data-driven runs from JSON / CSV iteration files. Use when HTTP API tests are authored in Postman and need to run, pace themselves, and report correctly in CI.
restassured-testing
Strengthens and speeds up JVM API test suites - assertions so loose that an empty `200 OK` passed for three days, a 220-test suite spending fourteen minutes mostly waiting on sequential HTTP calls, or endpoints exercised through a hand-rolled JDK HTTP client with JSON parsed by hand. Authors REST Assured (Java) tests in the given().when().then() BDD-style DSL: status code and JSON / XML path assertions, authentication (Basic, OAuth2, API key), Maven / Gradle dependencies, JUnit 5 execution, and Surefire / JaCoCo reports for CI gating. Use when the project is on the JVM and its API tests miss real assertions, run too slowly, or are written by hand.
restler-fuzzing
Runs stateful REST API fuzzing using Microsoft's RESTler - infers producer-consumer dependencies from an OpenAPI spec, drives sequences of requests (POST → GET → DELETE chains), and reports 5xx errors, resource leaks, and hierarchy violations. Wraps the canonical 4-stage workflow (compile → test → fuzz-lean → fuzz). Use when the API is stateful (resources are created, queried, modified, deleted) and Schemathesis's stateless fuzzing is missing the multi-step bugs.
schemathesis-fuzzing
Generates property-based API tests automatically from an OpenAPI 2/3.x or GraphQL schema using Schemathesis, runs them via the `schemathesis run` CLI or as a pytest decorator, configures the canonical checks (status_code_conformance, response_schema_conformance, content_type_conformance, response_headers_conformance, not_a_server_error), and gates CI on schema-conformance failures plus 5xx detection. Use when the project ships an OpenAPI or GraphQL schema and the team wants schema-driven coverage that scales as the API evolves.