CORE SYNTAX
Expressions and JavaScript
Overview
Karate expressions provide powerful dynamic capabilities through JavaScript integration, JsonPath short-cuts, XPath selectors, and special syntax. Understanding expressions is crucial for dynamic test logic, data processing, and advanced validation scenarios.
Expression Types and Shapes
Karate supports multiple expression types on the right-hand-side of assignments:
JavaScript Expressions
# Simple JavaScript expressions
* def myVar = 'hello'
* def calculation = 5 * 2 + 1
* def isValid = age >= 18 && name.length > 0
# Complex expressions with variable mixing
* def fullName = firstName + ' ' + lastName
* def isEligible = user.age >= 21 && user.status == 'active'
* def timestamp = new Date().getTime()
Function Expressions
# Inline function definition
* def randomId = function(){ return Math.floor(Math.random() * 1000) }
* def greeter = function(name) { return 'Hello ' + name + '!' }
# Multi-line function for complex logic
* def validator =
"""
function(user) {
if (!user.name || user.name.length < 2) return false;
if (!user.email || !user.email.includes('@')) return false;
return user.age >= 18;
}
"""
JsonPath Short-cuts
Karate provides convenient short-cuts for JsonPath expressions:
Variable JsonPath
* def data = {
users: [
{ name: 'Alice', age: 25 },
{ name: 'Bob', age: 30 }
]
}
# JsonPath short-cuts on variables
* def firstUser = $data.users[0]
* def allNames = $data.users[*].name
* def adultUsers = $data.users[?(@.age >= 21)]
# Equivalent to using get keyword
* def firstUser = get data.users[0]
* def allNames = get data.users[*].name
Response JsonPath
# Short-cut JsonPath on response ($ represents response)
* def userId = $.user.id
* def userNames = $.users[*].name
* def activeUsers = $.users[?(@.status == 'active')]
# Equivalent forms
* def userId = response.user.id
* def userId = get response.user.id
JsonPath Shape Summary
Shape | Description | Example |
---|---|---|
$.bar | Pure JsonPath on response | $.user.name |
$[0] | Array index on response | $.users[0] |
$foo.bar | JsonPath on variable foo | $data.users[0].name |
$foo[0] | Array access on variable foo | $users[0] |
Advanced JsonPath Filtering
* def products = [
{ name: 'laptop', price: 1000, category: 'electronics' },
{ name: 'book', price: 20, category: 'education' },
{ name: 'phone', price: 800, category: 'electronics' }
]
# Filter by condition
* def electronics = $products[?(@.category == 'electronics')]
* def expensiveItems = $products[?(@.price > 500)]
# Get specific fields from filtered results
* def electronicNames = $products[?(@.category == 'electronics')].name
* def prices = $products[*].price
# Dynamic filtering with karate.jsonPath()
* def categoryFilter = 'electronics'
* def filtered = karate.jsonPath(products, "$..[?(@.category=='" + categoryFilter + "')]")
XPath Expressions
Variable XPath
* def xmlData =
"""
<catalog>
<book id="1"><title>Karate Guide</title><price>29.99</price></book>
<book id="2"><title>API Testing</title><price>39.99</price></book>
</catalog>
"""
# XPath on variables
* def firstTitle = get xmlData /catalog/book[1]/title
* def allPrices = get xmlData /catalog/book/price
* def expensiveBooks = get xmlData /catalog/book[price > 30]
Response XPath
# Short-cut XPath on response
* def bookCount = /catalog/book/count()
* def firstBookTitle = /catalog/book[1]/title
* def maxPrice = /catalog/book/price[max()]
Embedded Expressions
Embedded expressions allow dynamic value injection using #()
syntax:
JSON Embedded Expressions
* def userId = 123
* def userName = 'alice'
* def timestamp = new Date().getTime()
# Inject variables into JSON
* def user = {
id: '#(userId)',
name: '#(userName)',
email: '#(userName + "@example.com")',
createdAt: '#(timestamp)',
profile: '#({ theme: "dark", language: "en" })'
}
* match user.id == 123
* match user.email == 'alice@example.com'
XML Embedded Expressions
* def productId = 'PROD-001'
* def productName = 'Laptop'
* def price = 999.99
* def product =
"""
<product>
<id>#(productId)</id>
<name>#(productName)</name>
<price>#(price)</price>
<timestamp>#(new Date().getTime())</timestamp>
</product>
"""
String Concatenation in Embedded Expressions
# ❌ Wrong - concatenation outside expression
* def greeting = { message: 'Hello #(name)' }
# ✅ Correct - full expression within #()
* def name = 'World'
* def greeting = { message: '#("Hello " + name)' }
* match greeting.message == 'Hello World'
# ✅ Alternative - two-step approach
* def message = 'Hello ' + name
* def greeting = { message: '#(message)' }
Embedded Expression Rules
Embedded expressions work in:
- JSON or XML declarations
- Right-hand side of
def
,match
,configure
- When reading JSON/XML files with
read()
- Must be enclosed in
#(
and)
Remove If Null Pattern
* def data = { a: 'hello', b: null, c: null }
* def json = {
foo: '#(data.a)',
bar: '#(data.b)',
baz: '##(data.c)' # Optional - removes key if null
}
* match json == { foo: 'hello', bar: null }
# Note: 'baz' key is completely removed
Self-Validation Expressions
Use #? EXPR
for dynamic validation where _
represents the current value:
Basic Self-Validation
* def user = { name: 'John', age: 25, score: 85 }
* match user == {
name: '#string',
age: '#? _ >= 18 && _ <= 100',
score: '#? _ > 0 && _ <= 100'
}
Complex Self-Validation
* def product = {
name: 'Laptop',
price: 999.99,
category: 'electronics',
tags: ['computer', 'portable']
}
* match product == {
name: '#? _.length > 2',
price: '#? _ > 0 && _ < 10000',
category: '#? ["electronics", "books", "clothing"].includes(_)',
tags: '#? _.length >= 1 && _.every(tag => typeof tag === "string")'
}
Cross-Field Validation
* def temperature = { celsius: 100, fahrenheit: 212 }
* match temperature == {
celsius: '#number',
fahrenheit: '#? _ == $.celsius * 1.8 + 32'
}
# Alternative using embedded expressions
* match temperature contains { fahrenheit: '#($.celsius * 1.8 + 32)' }
Enclosed JavaScript
Alternative to embedded expressions using parentheses around entire payload:
Enclosed vs Embedded
* def user = { name: 'john', age: 21 }
* def lang = 'en'
# Embedded expressions approach
* def embeddedResult = {
name: '#(user.name)',
locale: '#(lang)',
sessionUser: '#(user)'
}
# Enclosed JavaScript approach
* def enclosedResult = ({
name: user.name,
locale: lang,
sessionUser: user
})
* match embeddedResult == enclosedResult
When to Use Each Approach
Use Case | Recommended | Reason |
---|---|---|
File-based JSON templates | Embedded expressions | Maintains valid JSON for IDE editing |
Complex dynamic generation | Enclosed JavaScript | More natural for JS developers |
Validation scenarios | Embedded expressions | Better for schema-like patterns |
Simple variable substitution | Either | Personal preference |
Conditional Expressions
Ternary Operations
* def environment = karate.env || 'dev'
* def baseUrl = environment == 'prod' ? 'https://api.prod.com' : 'https://api.dev.com'
* def timeout = environment == 'prod' ? 30000 : 5000
* def debugMode = environment != 'prod'
Complex Conditional Logic
* def calculateDiscount =
"""
function(price, customerType, quantity) {
if (customerType === 'premium') {
return quantity > 10 ? price * 0.7 : price * 0.8;
} else if (customerType === 'regular') {
return quantity > 5 ? price * 0.9 : price * 0.95;
} else {
return price;
}
}
"""
* def finalPrice = calculateDiscount(100, 'premium', 15)
The Karate Object
Core Karate Methods
# Environment and configuration
* def env = karate.env
* def config = karate.get('config', {})
# Data manipulation
* def prettyJson = karate.pretty(response)
* def keys = karate.keysOf(user)
* def merged = karate.merge(obj1, obj2)
# Advanced JsonPath
* def filtered = karate.jsonPath(data, "$.users[?(@.age > 25)]")
* def firstMatch = karate.jsonPath(data, "$.users[0].name")
# Evaluation and execution
* def result = karate.eval('1 + 2 * 3')
* def dynamicCode = 'return Math.random()'
* def randomNum = karate.eval(dynamicCode)
String and Data Processing
# String operations
* def lowerData = karate.lowerCase(response)
* def extracted = karate.extract(htmlText, '<title>(.*)</title>', 1)
* def allMatches = karate.extractAll(logText, 'ERROR: (.*)', 1)
# Array operations
* def doubled = karate.map(numbers, function(x){ return x * 2 })
* def filtered = karate.filter(users, function(u){ return u.age >= 18 })
* def combined = karate.append(list1, list2, list3)
Type Conversion and Coercion
Number Conversion
# String to number
* def foo = '10'
* def numericFoo = 1 * foo
* def intFoo = parseInt(foo)
* def floatFoo = parseFloat('10.5')
# Force integer (double-tilde shortcut)
* def result = ~~(100 * 0.1) # Results in 10, not 10.0
Large Numbers
# Handle large numbers with BigDecimal
* def big = new java.math.BigDecimal(123123123123)
* def json = { num: '#(big)' }
* match json == { num: 123123123123 }
Advanced Expression Patterns
Dynamic Property Access
* def obj = { user: { profile: { name: 'John' } } }
* def path = 'user.profile.name'
* def value = karate.jsonPath(obj, '$.' + path)[0]
# Or using JavaScript property access
* def getValue =
"""
function(obj, path) {
return path.split('.').reduce((o, k) => o && o[k], obj);
}
"""
* def name = getValue(obj, 'user.profile.name')
Template Processing
* def template = {
id: '#(id)',
name: '#(name)',
status: '#(status || "active")'
}
# Use template with different values
* def id = 1
* def name = 'Alice'
* def user1 = template
* def id = 2
* def name = 'Bob'
* def status = 'pending'
* def user2 = template
Expression Evaluation Timing
# Embedded expressions evaluate when the JSON is created
* def timestamp = new Date().getTime()
* def template = { created: '#(timestamp)' }
* def obj1 = template # Uses current timestamp
# ... wait some time ...
* def obj2 = template # Uses same timestamp!
# For dynamic evaluation, use functions
* def template = { created: '#(new Date().getTime())' }
* def obj1 = template # Fresh timestamp
# ... wait some time ...
* def obj2 = template # Fresh timestamp
Best Practices
Expression Performance
# ✅ Good: Cache expensive computations
* def expensiveData = callonce read('load-data.feature')
* def processedData = karate.map(expensiveData.items, processingFunction)
# ✅ Good: Reuse compiled patterns
* def emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
* def isValidEmail = function(email) { return emailRegex.test(email) }
Expression Readability
# ✅ Good: Break complex expressions into steps
* def userAge = user.profile.age
* def isAdult = userAge >= 18
* def hasPermission = user.roles.includes('admin')
* def canAccess = isAdult && hasPermission
# ❌ Avoid: Complex one-liners
* def canAccess = user.profile.age >= 18 && user.roles.includes('admin')
Error Handling in Expressions
# Safe property access
* def name = karate.get('user.profile.name', 'Unknown')
* def age = user.profile ? user.profile.age : 0
# Validation before processing
* def processUser =
"""
function(user) {
if (!user || !user.id) {
karate.fail('Invalid user data');
}
return {
id: user.id,
displayName: user.name || 'Anonymous'
};
}
"""
Next Steps
- Learn about Actions and Keywords for test execution control
- Explore HTTP Requests for API testing with dynamic data
- Understand Assertions for powerful validation patterns