CORE SYNTAX
Data Types and Formats
Overview
Karate provides native support for complex data types, eliminating the need for string escaping and enabling seamless integration of JSON, XML, JavaScript functions, and file operations directly within test scripts.
Native Data Types Concept
Native data types mean you can insert them directly into scripts without string enclosure or quote escaping. They integrate seamlessly within your test flow, making scripts more readable and maintainable.
JSON Objects
Lenient JSON Parsing
# Keys don't require quotes (lenient parsing)
* def cat = { name: 'Billie', scores: [2, 5] }
* assert cat.scores[1] == 5
# Complex nested structures
* def user = {
profile: { name: 'John', age: 30 },
preferences: { theme: 'dark', notifications: true },
roles: ['user', 'admin']
}
Special Character Handling
# Hyphens require quotes (interpreted as minus sign)
* def headers = { 'Content-Type': 'application/json', 'X-API-Key': 'secret' }
# Standard JSON when needed
* def strictJson = {
"user-id": 123,
"created-at": "2023-01-01T00:00:00Z"
}
JSON Manipulation and Access
* def cats = [{ name: 'Billie' }, { name: 'Bob' }]
* match cats[1] == { name: 'Bob' }
# Extract parts for reuse
* def first = cats[0]
* match first == { name: 'Billie' }
# Nested property access
* def response = { user: { profile: { name: 'Alice' } } }
* def userName = response.user.profile.name
* match userName == 'Alice'
Best Practice: Always prefer
match
overassert
for JSON/XML comparisons. Match provides better error messages and supports embedded expressions and fuzzy matching.
XML Documents
Basic XML Handling
* def cat = <cat><name>Billie</name><scores><score>2</score><score>5</score></scores></cat>
# XPath indexing (starts from 1)
* match cat/cat/scores/score[2] == '5'
# JSON-like traversal (starts from 0)
* match cat.cat.scores.score[1] == 5
XML Navigation Flexibility
* def xmlDoc =
"""
<user>
<profile>
<name>John</name>
<age>30</age>
</profile>
<permissions>
<role>admin</role>
<role>user</role>
</permissions>
</user>
"""
# XPath style
* match xmlDoc/user/profile/name == 'John'
* match xmlDoc/user/permissions/role[1] == 'admin'
# JSON-like style (more intuitive for array access)
* match xmlDoc.user.permissions.role[0] == 'admin'
* match xmlDoc.user.permissions.role[1] == 'user'
Embedded Expressions
Dynamic Value Injection
* def user = { name: 'john', age: 21 }
* def lang = 'en'
# Inject variables into JSON using #() syntax
* def session = {
name: '#(user.name)',
locale: '#(lang)',
sessionUser: '#(user)',
timestamp: '#(new Date().getTime())'
}
* match session.name == 'john'
* match session.locale == 'en'
* match session.sessionUser == user
XML Embedded Expressions
* def user = <user><name>john</name></user>
* def lang = 'en'
* def session =
"""
<session>
<locale>#(lang)</locale>
<sessionUser>#(user)</sessionUser>
<timestamp>#(new Date().getTime())</timestamp>
</session>
"""
XPath Functions
Basic XPath Operations
Use XPath functions for advanced XML querying and validation:
* def records =
"""
<records>
<record index="1">a</record>
<record index="2">b</record>
<record index="3" foo="bar">c</record>
</records>
"""
# Count nodes
* match records count(/records//record) == 3
# Attribute selection
* match records //record[@index=2] == 'b'
* match records //record[@foo='bar'] == 'c'
# Text content
* match records //record[@index='1']/text() == 'a'
Advanced XPath Patterns
Node-Set Returns
XPath expressions returning multiple nodes work with match assertions:
* def teachers =
"""
<teachers>
<teacher department="science">
<subject>math</subject>
<subject>physics</subject>
</teacher>
<teacher department="arts">
<subject>political education</subject>
<subject>english</subject>
</teacher>
</teachers>
"""
# Returns array of matching nodes
* match teachers //teacher[@department='science']/subject == ['math', 'physics']
* match teachers //teacher[@department='arts']/subject == ['political education', 'english']
# All subjects across teachers
* match teachers //subject == ['math', 'physics', 'political education', 'english']
Dynamic XPath with karate.xmlPath()
Build XPath expressions dynamically:
* def xml = <query><name><foo>bar</foo></name></query>
* def elementName = 'name'
# Dynamic path construction
* def name = karate.xmlPath(xml, '/query/' + elementName + '/foo')
* match name == 'bar'
# Extract complex nodes
* def queryName = karate.xmlPath(xml, '/query/' + elementName)
* match queryName == <name><foo>bar</foo></name>
# With variables and conditions
* def department = 'science'
* def teachers = read('teachers.xml')
* def scienceTeachers = karate.xmlPath(teachers, "//teacher[@department='" + department + "']")
Complex XPath Queries
* def catalog =
"""
<catalog>
<book id="bk101" available="true">
<author>Gambardella, Matthew</author>
<title>XML Developer's Guide</title>
<price>44.95</price>
</book>
<book id="bk102" available="false">
<author>Ralls, Kim</author>
<title>Midnight Rain</title>
<price>5.95</price>
</book>
</catalog>
"""
# Books under $10
* match catalog //book[price < 10]/title == ['Midnight Rain']
# Available books
* match catalog //book[@available='true']/@id == ['bk101']
# Contains text
* match catalog //book[contains(title, 'XML')]/author == ['Gambardella, Matthew']
# Position-based
* match catalog //book[position()=1]/title == 'XML Developer\'s Guide'
* match catalog //book[last()]/title == 'Midnight Rain'
XPath with Namespaces
# For namespace-aware XML
* configure xmlNamespaceAware = true
* def soap =
"""
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<m:GetPrice xmlns:m="http://example.com/prices">
<m:Item>Apple</m:Item>
</m:GetPrice>
</soap:Body>
</soap:Envelope>
"""
# Access with namespace prefixes
* def item = karate.xmlPath(soap, "//m:Item")
* match item == 'Apple'
XPath Best Practices
# ✅ Good: Specific paths
* match xml //book[@id='bk101']/title == 'XML Developer\'s Guide'
# ❌ Avoid: Overly broad paths
* match xml //title == 'Some Title'
# ✅ Good: Use predicates for filtering
* match xml //book[price > 10 and @available='true']
# ✅ Good: Leverage XPath functions
* match xml count(//book) == 2
* match xml sum(//book/price) == 50.90
Cross-Reference: For XML validation patterns, see Match Keyword
Embedded Expression Rules
Embedded expressions work:
- Within JSON or XML declarations
- On the right-hand side of
def
,match
,configure
- When reading JSON/XML files with
read()
- Must be enclosed in
#(
and)
String Concatenation in Embedded Expressions
# ❌ Wrong - concatenation outside expression
* def foo = { bar: 'hello #(name)' }
# ✅ Correct - full expression within #()
* def name = 'World'
* def foo = { bar: '#("hello " + name)' }
* match foo.bar == 'hello World'
# ✅ Alternative - two-step approach
* def greeting = 'hello ' + name
* def foo = { bar: '#(greeting)' }
Match Statement Convenience
# Embedded expressions work in match statements
* def foo = 'a1'
* match foo == '#("a" + 1)'
# ES6 string interpolation (Karate 1.0+)
* def todaysDate = '2023-01-01'
* param filter = `ORDER_DATE:"${todaysDate}"`
Enclosed JavaScript
Alternative to Embedded Expressions
* def user = { name: 'john', age: 21 }
* def lang = 'en'
# Embedded expressions approach
* def embedded = {
name: '#(user.name)',
locale: '#(lang)',
sessionUser: '#(user)'
}
# Enclosed JavaScript approach (parentheses around entire payload)
* def enclosed = ({
name: user.name,
locale: lang,
sessionUser: user
})
* match embedded == enclosed
When to Use Each Approach
Use Case | Recommended Approach | 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 |
Multi-line Expressions
Readable Complex Structures
# Instead of single-line (hard to read)
* def cat = <cat><name>Billie</name><scores><score>2</score><score>5</score></scores></cat>
# Use multi-line for clarity
* def cat =
"""
<cat>
<name>Billie</name>
<scores>
<score>2</score>
<score>5</score>
</scores>
</cat>
"""
SOAP Request Example
* request
"""
<?xml version='1.0' encoding='UTF-8'?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
<S:Body>
<ns2:QueryUsageBalance xmlns:ns2="http://www.mycompany.com/usage/V1">
<ns2:UsageBalance>
<ns2:LicenseId>12341234</ns2:LicenseId>
</ns2:UsageBalance>
</ns2:QueryUsageBalance>
</S:Body>
</S:Envelope>
"""
Inline Validation
* match response ==
"""
{
id: { domain: "DOM", type: "entityId", value: "#ignore" },
created: { on: "#ignore" },
lastUpdated: { on: "#ignore" },
entityState: "ACTIVE"
}
"""
JavaScript Functions
Inline Function Definition
# Simple function
* def greeter = function(title, name) {
return 'hello ' + title + ' ' + name
}
* assert greeter('Mr.', 'Bob') == 'hello Mr. Bob'
# Function with complex logic
* def calculateDiscount = function(price, customerType) {
if (customerType === 'premium') {
return price * 0.8
} else if (customerType === 'regular') {
return price * 0.9
} else {
return price
}
}
* def discountedPrice = calculateDiscount(100, 'premium')
* match discountedPrice == 80
Multi-line Function Definition
* def dateStringToLong =
"""
function(s) {
var SimpleDateFormat = Java.type('java.text.SimpleDateFormat');
var sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
return sdf.parse(s).time;
}
"""
* assert dateStringToLong("2016-12-24T03:39:21.081+0000") == 1482550761081
Java Interop
# Calling Java classes from JavaScript
* def generateUuid =
"""
function() {
var UUID = Java.type('java.util.UUID');
return UUID.randomUUID().toString();
}
"""
* def uuid = generateUuid()
* match uuid == '#regex [a-f0-9-]{36}'
# Working with Java collections
* def createList =
"""
function(items) {
var ArrayList = Java.type('java.util.ArrayList');
var list = new ArrayList();
items.forEach(function(item) {
list.add(item);
});
return list;
}
"""
Function Call Alternatives
* def timeLong = call dateStringToLong '2016-12-24T03:39:21.081+0000'
* assert timeLong == 1482550761081
# JSON argument example
* def greeter = function(name) {
return 'Hello ' + name.first + ' ' + name.last + '!'
}
* def greeting = call greeter { first: 'John', last: 'Smith' }
* match greeting == 'Hello John Smith!'
Reading Files
File Type Auto-detection
# JSON files (auto-parsed)
* def someJson = read('some-json.json')
* def moreJson = read('classpath:more-json.json')
# XML files (auto-parsed)
* def someXml = read('../common/my-xml.xml')
# YAML to JSON conversion
* def jsonFromYaml = read('some-data.yaml')
# CSV to JSON conversion
* def jsonFromCsv = read('some-data.csv')
# Plain text
* def someString = read('classpath:messages.txt')
# JavaScript (evaluated)
* def someValue = read('some-js-code.js')
Path Prefixes
Prefix | Description | Use Case |
---|---|---|
classpath: | Relative to classpath root | Reusable across project |
this: | Relative to current feature | Called feature resources |
file: | Absolute file path | Development only |
(none) | Relative to current feature | Local resources |
JavaScript Function Files
# If JS file evaluates to function, it can be reused
* def someFunction = read('classpath:some-reusable-code.js')
* def result1 = call someFunction
* def result2 = someFunction() # Direct invocation
# One-liner function call
* def result = call read('some-js-code.js')
Feature File Reuse
# Perfect for authentication or setup flows
* def result = call read('classpath:some-reusable-steps.feature')
Dynamic File Selection
# Choose file based on condition
* def environment = karate.env || 'dev'
* def config = read('my-config-' + environment + '.json')
# Combine multiple files
* def combinedString = read('first.txt') + read('second.txt')
Binary Files and Streams
# Files without recognized extensions treated as streams
* def pdfStream = read('some-pdf.pdf')
* def imageBytes = read('screenshot.png')
# GraphQL files (treated as text)
* def query = read('user-query.graphql')
Request and Response Usage
# Use file as request body
* request read('some-big-payload.json')
# Use file in match validation
* match response == read('expected-response-payload.json')
# Raw string reading (bypasses auto-conversion)
* def csvString = karate.readAsString('classpath:my.csv')
* header Content-Type = 'text/csv'
* request csvString
Embedded Expressions in Files
JSON and XML files support embedded expressions when loaded:
user-template.json:
{
"id": "#(userId)",
"name": "#(userName)",
"email": "#(userName + '@example.com')",
"timestamp": "#(new Date().getTime())"
}
Usage:
* def userId = 123
* def userName = 'alice'
* def user = read('user-template.json')
* match user.id == 123
* match user.email == 'alice@example.com'
Type Conversion
String to Data Type Conversion
# YAML string to JSON
* text yamlString =
"""
name: John
age: 30
skills:
- java
- javascript
"""
* yaml userData = yamlString
* match userData.skills[1] == 'javascript'
# CSV string to JSON array
* text csvString =
"""
name,age,role
Alice,25,developer
Bob,30,manager
"""
* csv users = csvString
* match users[0] == { name: 'Alice', age: 25, role: 'developer' }
Raw Text Handling
# Disable auto-parsing with 'text' keyword
* text graphqlQuery =
"""
{
user(id: "<userId>") {
name
email
}
}
"""
# Use in request without JSON parsing
* def requestBody = { query: '#(graphqlQuery)' }
Advanced Data Patterns
Conditional Data Structures
* def includeAddress = true
* def user = {
name: 'John',
age: 30
}
* if (includeAddress) user.address = { city: 'New York', zip: '10001' }
Dynamic Property Names
* def fieldName = 'customField'
* def value = 'customValue'
* def data = {}
* eval data[fieldName] = value
* match data.customField == 'customValue'
Complex Nested Structures
* def buildNestedObject =
"""
function(levels, value) {
if (levels.length === 1) {
var obj = {};
obj[levels[0]] = value;
return obj;
}
var obj = {};
obj[levels[0]] = buildNestedObject(levels.slice(1), value);
return obj;
}
"""
* def nested = buildNestedObject(['a', 'b', 'c'], 'deep value')
* match nested == { a: { b: { c: 'deep value' } } }
Best Practices
File Organization
src/test/java/
├── data/
│ ├── users/
│ │ ├── valid-user.json
│ │ └── invalid-user.json
│ └── responses/
│ ├── success-response.json
│ └── error-response.json
├── utils/
│ ├── common-functions.js
│ └── data-generators.js
└── features/
└── user-tests.feature
Data Reusability
# ✅ Good: Reusable templates with embedded expressions
* def userTemplate = read('classpath:data/user-template.json')
* def testUser1 = { userId: 1, userName: 'alice' }
* def user1 = call read('classpath:utils/merge-template.js') { template: userTemplate, data: testUser1 }
# ✅ Good: Environment-specific data
* def environment = karate.env || 'dev'
* def config = read('classpath:config-' + environment + '.json')
Performance Considerations
# ✅ Good: Cache expensive operations
Background:
* def commonData = callonce read('classpath:setup-data.feature')
* def utilities = callonce read('classpath:utils/common-functions.js')
# ✅ Good: Reuse parsed data
* def baseRequest = read('classpath:data/base-request.json')
* def request1 = karate.merge(baseRequest, { id: 1 })
* def request2 = karate.merge(baseRequest, { id: 2 })
Next Steps
- Learn about Expressions and JavaScript for advanced data manipulation
- Explore Actions and Keywords for test execution control
- Understand HTTP Requests for API testing with data types