You have a Maven project and a karate-config.js that exposes baseUrl. Now you write tests. A Karate feature file is a plain text file with a .feature extension. It contains one Feature block and any number of Scenario blocks. Each scenario is a self-contained test — it sets up its request, fires it, and asserts on the response. No other files are required.
The anatomy of a feature file
Feature: User API Tests
Background:
* url baseUrl
* header Accept = 'application/json'
Scenario: Get all users
Given path 'users'
When method get
Then status 200
And match response == '#array'
And match each response contains { id: '#number', name: '#string' }
Scenario: Create a new user
Given path 'users'
And request { name: 'Alice Smith', email: 'alice@test.com', role: 'admin' }
When method post
Then status 201
And match response contains { id: '#number', name: 'Alice Smith' }The Feature keyword is a label — it names the file and appears in the HTML report. The Background block runs before every scenario in the file. Each Scenario is a test case.
Background — shared setup
Background is equivalent to a @BeforeEach in JUnit. Everything inside it runs once before each scenario, so it's the right place for configuration that every test in the file shares: the base URL, default headers, common variables.
Background:
* url baseUrl
* header Accept = 'application/json'
* header Content-Type = 'application/json'Unlike Cucumber's Background, Karate's supports any keyword — not just Given. The * prefix works everywhere.
Built-in keywords
Karate has a compact vocabulary. These are the ones you'll use in every feature file:
url — sets the full base URL for subsequent requests. Usually set in Background from the baseUrl config variable. Can be overridden per scenario.
path — appends segments to the URL. path 'users' resolves to baseUrl + '/users'. Multiple segments: path 'users', userId resolves to baseUrl + '/users/42' (where userId is a variable holding 42).
param — adds a query parameter. param role = 'admin' appends ?role=admin to the URL.
request — sets the request body. Inline JSON: request { name: 'Alice', role: 'admin' }. Variables and expressions are supported directly in the JSON without escaping.
method — fires the HTTP request. method get, method post, method put, method patch, method delete. This is the step that actually sends the call.
status — asserts the HTTP response status code. status 200, status 201, status 404.
match — Karate's assertion keyword. match response.name == 'Alice'. Every assertion in Karate goes through match.
response — a built-in variable automatically set to the parsed response body after every method call. For JSON APIs it's a JavaScript object; access fields with dot notation.
header — set a request header (in Background or before method) or assert a response header (after method). Context determines direction.
def — define a variable. * def userId = response.id. Variables persist for the whole scenario and can be used in subsequent requests.
Step keywords — Given, When, Then, And, *
In Cucumber, step keywords must match the step definition type exactly. In Karate, they're all equivalent — they're cosmetic labels that make the scenario read like English. The * prefix is a wildcard that works anywhere, especially useful in Background where "Given I set the URL" feels forced.
Convention: use Given for request setup, When for the HTTP method call, Then and And for assertions. This mirrors the pattern from the API Testing Masterclass and keeps scenarios readable at a glance.
A complete feature file
Feature: User API Tests
Background:
* url baseUrl
* header Accept = 'application/json'
* header Content-Type = 'application/json'
Scenario: Get all users returns a list
Given path 'users'
When method get
Then status 200
And match response == '#array'
And match response[0] contains { id: '#number', name: '#string' }
Scenario: Get a user by ID
Given path 'users', 1
When method get
Then status 200
And match response.id == 1
And match response.name == '#string'
And match response.email == '#string'
Scenario: Create a user and confirm the response
Given path 'users'
And request { name: 'Bob Smith', email: 'bob@test.com', role: 'tester' }
When method post
Then status 201
And match response.id == '#number'
And match response.name == 'Bob Smith'
Scenario: Non-existent user returns 404
Given path 'users', 99999
When method get
Then status 404Four scenarios. Four independent tests. No shared state between them except the Background setup. If the third scenario fails, the fourth still runs.
How Karate executes a feature file
⚠️ Common mistakes
- Putting
methodbeforepathorrequest. Karate builds the request from the top down and fires it atmethod. Anypath,param, orrequeststep aftermethodis talking about the next request, not this one. Write setup steps first,methodlast, assertions after. - Expecting Background to share state between scenarios. Background re-runs for each scenario — variables defined in Background are fresh copies. If Scenario 1 defines
* def userId = 42in Background, Scenario 2 gets a fresh evaluation of the same line. Use Background for static setup (URL, headers) not for values that depend on a previous scenario's response. - Using Cucumber-style step definitions alongside Karate. Karate features don't need (and can't use) a separate
steps.javafile. If you create one expecting Cucumber integration, it will be silently ignored. Karate's built-in keywords replace all step definitions — there's nothing to fill in.
🎯 Practice task
Write a complete feature file with four scenarios against a real API. 30–40 minutes.
- In your Karate project's
users/package, createusers.feature. Setkarate-config.jsto point athttps://jsonplaceholder.typicode.com. - Write a
Backgroundblock withurl baseUrland anAcceptheader. - Write a scenario that GETs
/usersand assertsstatus 200andmatch response == '#array'. Run it green. - Write a scenario that GETs
/users/1and asserts thenamefield equals'Leanne Graham'andemailis a string. Run it green. - Write a scenario that POSTs a new user body to
/usersand assertsstatus 201andmatch response.id == '#number'. JSONPlaceholder returns a fake201with a generated id — confirm the id is in the response. - Write a negative scenario: GET
/users/99999and assertstatus 404. Run it. (JSONPlaceholder returns{}with a 404 for missing IDs.) - Stretch: add
* print responseas the last step in one scenario and run it. Observe the pretty-printed JSON in the console. Then remove the print — it's a debugging tool, not something you leave in committed tests.
Next lesson: running your tests from Maven and IntelliJ, reading the HTML report, and using Karate's print statement to debug failing scenarios.