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.
api-testing-overview
Teaches API testing from zero: what functional API testing covers, how it differs from contract testing and load testing, and a decision table that picks one tool from observable project facts (language and build file, whether an OpenAPI or GraphQL schema exists, functional vs spec-conformance fuzzing vs stateful security fuzzing, whether non-engineers read the tests). Names the real options (Postman with newman, REST Assured, Karate, Tavern, Schemathesis, RESTler), gives install and first-run commands with what a passing run looks like, and the traps that bite first: asserting only on HTTP status, order-dependent tests sharing server state, and hardcoded environment URLs and secrets. Use when an HTTP API needs automated tests and no tool has been chosen, or when an inherited suite only checks status codes.
postman-collections
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 uses iteration data files (JSON / CSV) for data-driven runs. Use when the project ships HTTP API tests authored in Postman and the team needs CI execution alongside or instead of the Postman desktop runner.
restassured-testing
Authors REST Assured (Java) API tests using the given().when().then() BDD-style DSL - status code + JSON/XML path assertions + authentication (Basic, OAuth2, API key). Configures Maven / Gradle dependencies, runs via JUnit 5, and emits Surefire / JaCoCo reports for CI gating. Use when the project is on the JVM and wants type-safe API tests in the app's own language; for a Gherkin feature-file flow on the same JVM use karate-testing, for YAML tests on the pytest stack use tavern-testing.
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.
tavern-testing
Authors Tavern API tests as YAML files (`test_*.tavern.yaml`) with `test_name` + `stages` + `request` + `response` blocks, runs them through the Tavern pytest plugin (auto-discovered), and gates CI on the resulting JUnit XML. Covers RESTful, MQTT, and gRPC variants. Use when the project runs on pytest and prefers YAML over a Python- or Java-DSL; on the JVM use karate-testing or restassured-testing instead, and for schema-driven property-based fuzzing on the same pytest stack use schemathesis-fuzzing.