The API Testing Masterclass lesson on HTTP methods taught you what GET, POST, PUT, PATCH, and DELETE mean at the protocol level. This lesson is the Karate implementation of that knowledge: the exact syntax for each verb, how to set path segments and query parameters, how to chain multiple calls in one scenario, and how to pass values between them. By the end you'll have a complete CRUD flow running in a single feature file.
GET requests
A basic GET:
Scenario: Get a single user
Given url baseUrl
And path 'users', 1
When method get
Then status 200path 'users', 1 appends segments to the base URL. Each comma-separated value becomes a path segment, URL-encoded automatically. The result: baseUrl + '/users/1'. Use this pattern instead of string concatenation — it handles special characters safely.
GET with query parameters:
Scenario: Search users by role
Given url baseUrl
And path 'users'
And param role = 'admin'
And param active = true
And param page = 1
When method get
Then status 200Each param call adds one query string key-value pair: ?role=admin&active=true&page=1. For multiple values on the same key (e.g., ?tag=java&tag=qa), use params with an array: And params { tag: ['java', 'qa'] }.
POST requests
A POST with a JSON body:
Scenario: Create a new user
Given url baseUrl
And path 'users'
And request { name: 'Alice', email: 'alice@test.com', role: 'admin' }
When method post
Then status 201
And match response.id == '#number'
And match response.name == 'Alice'The request keyword accepts an inline JSON object, a variable, or a file reference (request read('user.json')). Karate automatically sets Content-Type: application/json when the request body is a JSON object — no manual header required.
PUT and PATCH
PUT replaces the entire resource:
Scenario: Update a user's full record
Given url baseUrl
And path 'users', 1
And request { name: 'Alice Smith', email: 'alice.smith@test.com', role: 'admin' }
When method put
Then status 200
And match response.name == 'Alice Smith'PATCH updates only the fields you send:
Scenario: Patch the user's role
Given url baseUrl
And path 'users', 1
And request { role: 'viewer' }
When method patch
Then status 200
And match response.role == 'viewer'The distinction maps directly to what you learned in the API Testing Masterclass: PUT is idempotent and replaces the whole document; PATCH is a partial update. Karate treats them identically — method put and method patch just change the HTTP verb.
DELETE requests
Scenario: Delete a user
Given url baseUrl
And path 'users', 1
When method delete
Then status 204DELETE typically has no request body and returns 204 No Content. The request keyword is omitted — Karate sends an empty body by default when request is absent.
Headers per request
To set a header on one specific request (not globally via Background):
And header Authorization = 'Bearer ' + authToken
And header X-Custom-Header = 'value'If the header applies to all requests in a feature file, set it in Background. If it applies to the entire test run, set it in karate-config.js with configure headers.
Chaining calls with def
Real API workflows chain calls: create a resource, use its ID to fetch it, update it, delete it. Karate uses * def to store values between requests:
Scenario: Full CRUD flow
# CREATE
Given path 'users'
And request { name: 'Test User', email: 'test@test.com', role: 'tester' }
When method post
Then status 201
* def userId = response.id
# READ
Given path 'users', userId
When method get
Then status 200
And match response.name == 'Test User'
# UPDATE
Given path 'users', userId
And request { name: 'Updated User', email: 'test@test.com', role: 'tester' }
When method put
Then status 200
And match response.name == 'Updated User'
# DELETE
Given path 'users', userId
When method delete
Then status 204* def userId = response.id captures the id from the POST response. That userId variable is then available in every subsequent step of the same scenario. In Rest Assured you'd use extract().path("id") to do the same thing — in Karate it's one line.
The CRUD flow, visualised
⚠️ Common mistakes
- Using
urlinstead ofpathfor sub-paths. After settingurl baseUrlin Background, usepath 'users'to append segments — noturl baseUrl + '/users'. String-concatenating URLs works but bypasses Karate's URL encoding and looks like an antipattern in code review. Reserveurlfor when you need to override the base URL for a single request (e.g., calling an external service in one scenario). - Forgetting that
defvariables are scenario-scoped. AuserIddefined with* defin Scenario 1 is not available in Scenario 2. If you need the same resource across scenarios, either generate it in a Backgroundcallto a reusable feature or set it up per-scenario. Sharing mutable state between scenarios creates brittle tests that fail in random order. - Missing
requeston POST/PUT and wondering why the body is empty. If you omitAnd request {...}beforemethod post, Karate sends an empty body. The server often returns a400 Bad Requestor ignores required fields. The fix is always the same: add therequeststep beforemethod.
🎯 Practice task
Write a complete CRUD flow against JSONPlaceholder. 35–45 minutes.
- In
users/users.feature, write aBackgroundwithurl baseUrlpointing athttps://jsonplaceholder.typicode.com. - Write a GET scenario:
path 'users', 1, assertstatus 200, assertresponse.id == 1andresponse.name == '#string'. - Write a GET with query parameters:
path 'users', addparam _limit = 3, assertstatus 200andmatch response == '#array'andkarate.sizeOf(response) == 3. - Write a POST: send
{ name: 'Test', email: 'test@test.com', username: 'tester' }topath 'users', assertstatus 201, assertresponse.id == '#number'. - Write the full CRUD chain in one scenario — POST to create, store the id, GET to read, PUT to update (assert the name changed), DELETE (assert 204). JSONPlaceholder doesn't persist data, but the HTTP responses are realistic.
- Negative case: write a scenario that GETs
path 'users', 0and assertsstatus 404. Confirm the test passes even though404is an error status — Karate does not auto-fail on non-2xx. - Stretch: write a PATCH scenario that sends only
{ name: 'Patched Name' }and assertsstatus 200andresponse.name == 'Patched Name'. Compare the request body size with the PUT scenario from step 5.
Next lesson: Karate's assertion system — match, match contains, fuzzy matchers, and match each for arrays.