Every Karate scenario is a short JavaScript runtime. Variables hold strings, numbers, booleans, JSON objects, and arrays. Expressions compute new values from existing ones. Embedded expressions inject variables into JSON bodies and header strings. This is the plumbing that turns static feature files into dynamic, realistic tests — and the building block for everything else in this chapter.
Defining variables with def
def creates a variable. The value can be any JSON-compatible type or any JavaScript expression:
* def name = 'Alice'
* def statusCode = 200
* def isActive = true
* def user = { name: 'Alice', email: 'alice@test.com', role: 'admin' }
* def tags = ['admin', 'tester']Variables are scoped to the scenario. A def in Scenario 1 is not visible in Scenario 2. A def in Background is re-evaluated for every scenario — it's not shared state.
JavaScript expressions in def
Karate evaluates the right-hand side of every def as JavaScript. That means any JavaScript expression is valid:
* def timestamp = java.lang.System.currentTimeMillis()
* def uniqueEmail = 'test-' + timestamp + '@test.com'
* def randomId = Math.floor(Math.random() * 10000)
* def upperName = 'alice'.toUpperCase()
* def itemCount = [1, 2, 3].lengthjava.lang.System.currentTimeMillis() calls into the JVM directly — Karate has a bridge to Java that lets you call any Java class. This is the standard way to generate unique values for test data that won't collide across parallel runs.
Embedded expressions — #(variable)
To use a variable inside a JSON object or a string, use the #(...) syntax. Karate replaces the placeholder with the variable's value at runtime:
* def email = 'alice@test.com'
* def role = 'admin'
* def body = { name: 'Alice', email: '#(email)', role: '#(role)' }
Given request bodyThe resulting request body is { "name": "Alice", "email": "alice@test.com", "role": "admin" }. The markers are substituted before the request is sent.
Embedded expressions in strings work the same way:
* def authToken = 'abc123xyz'
* header Authorization = 'Bearer #(authToken)'This produces Authorization: Bearer abc123xyz. No string concatenation needed — the #(...) marker handles it.
Embedded expressions in JSON bodies
You can mix literal values and variable references in a single JSON body:
* def userId = 42
* def amount = 99.99
* def body = { userId: '#(userId)', amount: '#(amount)', currency: 'GBP', confirmed: true }
Given request bodyuserId and amount come from variables; currency and confirmed are literals. Karate substitutes only the #(...) markers.
For numbers, Karate inserts the numeric value (not a string) when the variable holds a number. { id: '#(userId)' } with userId = 42 produces { "id": 42 }, not { "id": "42" }.
Dynamic variables with karate.set() and karate.get()
When a variable name is computed at runtime — for example, in a loop — you can't use * def because the name is a string. Use karate.set() and karate.get() instead:
* karate.set('dynamicVar', response.id)
* def id = karate.get('dynamicVar')This is unusual in normal feature files but common in called feature files that process collections or in utility scripts.
The table keyword — inline tabular data
table creates a list of objects from an inline table, without a CSV file:
* table users
| name | email | role |
| 'Alice' | 'alice@test.com' | 'admin' |
| 'Bob' | 'bob@test.com' | 'tester'|
* def firstUser = users[0]
And match firstUser.name == 'Alice'users is now an array of objects: [{ name: 'Alice', email: 'alice@test.com', role: 'admin' }, ...]. Each column header becomes a field name; each row is one object. Use table for small inline datasets; use CSV files (next lesson) for larger ones.
Variable types at a glance
Karate variable types and embedded expression syntax
| How to define | Embedded in JSON | Example | |
|---|---|---|---|
| String | * def name = 'Alice' | { user: '#(name)' } | Produces { "user": "Alice" } |
| Number / Boolean | * def id = 42 * def flag = true | { id: '#(id)', active: '#(flag)' } | Produces { "id": 42, "active": true } — correct types |
| Object | * def user = { name: 'Alice', role: 'admin' } | Given request user — send the whole object | Or use fields: { owner: '#(user)' } nests the object |
| Array | * def tags = ['admin', 'tester'] | { tags: '#(tags)' } | Produces { "tags": ["admin", "tester"] } |
| Expression | * def email = 'test-' + java.lang.System.currentTimeMillis() + '@test.com' | { email: '#(email)' } | Unique value per run — no collision risk in parallel tests |
⚠️ Common mistakes
- Using
#(variable)in amatchassertion. Embedded expressions are for building request data, not for comparing response data.match response.name == '#(name)'does NOT match ifresponse.nameequals the value ofname— it literally checks for the string#(name). To assert equality, writematch response.name == name(no hash, no parens). - Expecting
defto update across scenarios. Adefin Scenario 1 does not affect Scenario 2. If you define* def userId = 42in Scenario 1 and try to referenceuserIdin Scenario 2 without redefining it, you getnull. Use Background for values all scenarios need, or redefine per scenario. - Forgetting that
tablevalues are strings unless quoted. In atable, every cell value is evaluated as a Karate expression.| Alice |(without quotes) is treated as a variable reference and will benullifAliceis not defined.| 'Alice' |(with quotes) is the string literalAlice. Always quote string values in table cells.
🎯 Practice task
Build and use dynamic test data in a real scenario. 30–40 minutes.
- In your Karate project, create a scenario that defines
* def timestamp = java.lang.System.currentTimeMillis()and* def uniqueEmail = 'user-' + timestamp + '@test.com'. Print both with* print uniqueEmail. Run and confirm the email is unique each time. - Build a request body using embedded expressions:
* def body = { name: 'Test User', email: '#(uniqueEmail)', role: 'tester' }. POST it to JSONPlaceholder/users. Assertstatus 201. Confirm Karate substituted the email correctly by printingresponse. - Use
tableto define three users inline. Accessusers[0].nameandusers[2].rolewithmatchassertions. Confirm the types are strings (not nulls). - Write a header using an embedded expression:
* def token = 'fake-token-123', then* header Authorization = 'Bearer #(token)'. Run any GET and confirm the header appears in the Karate HTML report's request detail. - Prove variable isolation. Define
* def marker = 'scenario-1'in Scenario 1. In Scenario 2, attempt* print markerwithout redefining it. Confirm the output isnull— this is the expected behaviour and proves scenarios are isolated. - Stretch: use
karate.set('dynamicKey', response.id)after a POST, then* def savedId = karate.get('dynamicKey'). AssertsavedId == '#number'. This is the same asdef savedId = response.idbut shows the dynamic API — useful in looping utility features.
Next lesson: reading test data from external JSON and CSV files — keeping test data out of feature files and making it reusable across scenarios.