Your First Feature File — Given-When-Then for APIs

9 min read

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 404

Four 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 method before path or request. Karate builds the request from the top down and fires it at method. Any path, param, or request step after method is talking about the next request, not this one. Write setup steps first, method last, 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 = 42 in 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.java file. 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.

  1. In your Karate project's users/ package, create users.feature. Set karate-config.js to point at https://jsonplaceholder.typicode.com.
  2. Write a Background block with url baseUrl and an Accept header.
  3. Write a scenario that GETs /users and asserts status 200 and match response == '#array'. Run it green.
  4. Write a scenario that GETs /users/1 and asserts the name field equals 'Leanne Graham' and email is a string. Run it green.
  5. Write a scenario that POSTs a new user body to /users and asserts status 201 and match response.id == '#number'. JSONPlaceholder returns a fake 201 with a generated id — confirm the id is in the response.
  6. Write a negative scenario: GET /users/99999 and assert status 404. Run it. (JSONPlaceholder returns {} with a 404 for missing IDs.)
  7. Stretch: add * print response as 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.

// tip to track lessons you complete and pick up where you left off across devices.