ADVANCED
Conditional Logic
Implement if statements, ternary operators, and decision trees to make your Karate tests adapt dynamically to API responses and runtime conditions.
Benefits of Conditional Logic
- Adaptive testing: Handle varying API responses without duplicate test files
- Environment switching: Execute different logic for dev, staging, and production
- Error handling: Skip or fail tests gracefully based on runtime conditions
- Data transformation: Process and reshape JSON with map, filter, and forEach operations
Simple If Statements
Use inline if
statements for basic conditional execution.
Scenario: Basic if condition
* def status = 'active'
* if (status == 'active') karate.log('User is active')
* if (status != 'active') karate.fail('Expected active user')
Ternary Operators
Use ternary operators for concise conditional assignments.
Scenario: Conditional assignment
* def env = karate.env || 'dev'
* def baseUrl = env == 'prod' ? 'https://api.prod.com' : 'http://localhost:3000'
* def timeout = env == 'prod' ? 10000 : 5000
* print 'Using:', baseUrl, 'with timeout:', timeout
Avoid deeply nested ternaries as they reduce readability. Use JavaScript functions for complex logic.
Controlling Test Flow
Using karate.abort()
Exit a scenario without marking it as failed.
Scenario: Skip test conditionally
* def env = karate.env || 'dev'
* if (env == 'local') karate.abort()
Given url 'https://api.example.com'
And path '/users'
When method get
Then status 200
Use karate.abort()
to skip tests in specific environments or when preconditions are not met.
Using karate.fail()
Explicitly fail a test with a custom message.
Scenario: Fail with descriptive message
Given url 'https://api.example.com'
And path '/operation'
When method post
Then status 200
* if (!response.success) karate.fail('Operation failed: ' + response.error)
* if (response.data.length == 0) karate.fail('No data returned from API')
Always use descriptive error messages with karate.fail()
to make debugging easier in test reports.
Multi-line Conditional Blocks
For complex logic, use eval
blocks with JavaScript.
Scenario: Multi-line conditional logic
Given url 'https://api.example.com/status'
When method get
Then status 200
* eval
"""
if (response.status == 'pending') {
karate.log('Status is pending, waiting...');
java.lang.Thread.sleep(2000);
} else if (response.status == 'failed') {
karate.fail('Unexpected failure: ' + response.error);
}
"""
Environment-Based Logic
Adapt tests to different environments using karate.env
.
Scenario: Environment-aware configuration
* def env = karate.env || 'dev'
* def config =
"""
{
'prod': { url: 'https://api.prod.com', ssl: true },
'staging': { url: 'https://api.staging.com', ssl: true },
'dev': { url: 'http://localhost:3000', ssl: false }
}
"""
* def settings = config[env]
* if (settings.ssl) configure ssl = true
Given url settings.url
And path '/users'
When method get
Then status 200
* if (env == 'prod') match response.version == 'v2.0'
* if (env == 'dev') match response.debug == '#present'
Set karate.env
in CI/CD pipelines and validate in karate-config.js
to avoid environment mismatches.
Feature Flags
Handle feature flags dynamically in your tests.
Scenario: Test based on feature flags
Given url 'https://api.example.com/features'
When method get
Then status 200
* def features = response
# Test new feature if enabled
* if (features.newCheckout?.enabled) call read('test-new-checkout.feature')
* if (!features.newCheckout?.enabled) call read('test-legacy-checkout.feature')
# Skip tests for disabled features
* if (!features.analytics?.enabled) karate.abort()
# Conditional validation
* def expectedFields = features.extendedProfile?.enabled ? ['name', 'email', 'phone', 'address'] : ['name', 'email']
* match response.user contains only expectedFields
Use optional chaining (?.
) to safely access feature flag properties and avoid null errors.
Switch-Case Logic
Implement switch-case patterns with JavaScript functions.
Scenario: Handle multiple statuses
* def handleStatus =
"""
function(status) {
switch(status) {
case 'pending': return { action: 'wait', timeout: 5000 };
case 'processing': return { action: 'poll', interval: 1000 };
case 'completed': return { action: 'validate' };
case 'failed': return { action: 'retry', maxAttempts: 3 };
default: return { action: 'error', message: 'Unknown status' };
}
}
"""
Given url 'https://api.example.com/task/123'
When method get
Then status 200
* def decision = handleStatus(response.status)
* if (decision.action == 'wait') java.lang.Thread.sleep(decision.timeout)
* if (decision.action == 'error') karate.fail(decision.message)
Decision Trees
Build complex decision trees for advanced scenarios.
Scenario: Complex decision tree
* def makeDecision =
"""
function(context) {
if (context.env === 'prod') {
if (context.userType === 'admin') {
return { test: 'full', validate: 'strict' };
} else {
return { test: 'limited', validate: 'normal' };
}
} else if (context.env === 'staging') {
return { test: 'experimental', validate: 'loose' };
}
return { test: 'basic', validate: 'minimal' };
}
"""
* def context = { env: karate.env || 'dev', userType: 'admin' }
* def decision = makeDecision(context)
* print 'Decision:', decision
* if (decision.test == 'full') call read('full-test-suite.feature')
* if (decision.validate == 'strict') call read('strict-validation.feature')
JSON Transforms
Transform JSON data using functional-style operations.
Map and Filter Operations
Scenario: Transform array data with map and filter
* def users = [
{ name: 'John', age: 25, active: true },
{ name: 'Jane', age: 30, active: false },
{ name: 'Bob', age: 35, active: true }
]
# Filter only active users using ES6 arrow functions
* def activeUsers = users.filter(x => x.active)
* match activeUsers == '#[2]'
* match activeUsers[0].name == 'John'
# Transform to extract names only
* def names = users.map(x => x.name)
* match names == ['John', 'Jane', 'Bob']
# Transform to different shape
* def transformed = users.map(x => ({ id: x.name, years: x.age }))
* match transformed[0] == { id: 'John', years: 25 }
Karate Transform Functions
Scenario: Using karate transform functions
# karate.mapWithKey converts primitives to objects
* def names = ['Alice', 'Bob', 'Charlie']
* def users = karate.mapWithKey(names, 'name')
* match users == [{ name: 'Alice' }, { name: 'Bob' }, { name: 'Charlie' }]
# karate.filterKeys selects specific properties
* def schema = { a: '#string', b: '#number', c: '#boolean' }
* def response = { a: 'x', c: true }
* match response == karate.filterKeys(schema, response)
# karate.merge combines objects
* def base = { a: 1 }
* def extended = karate.merge(base, { b: 2, c: 3 })
* match extended == { a: 1, b: 2, c: 3 }
ForEach Operations
Scenario: Iterate with forEach
* def results = []
* def items = [{ id: 1, value: 10 }, { id: 2, value: 20 }]
# Process each item
* items.forEach(item => karate.appendTo(results, item.value * 2))
* match results == [20, 40]
# karate.forEach works on object key-values too
* def keys = []
* def vals = []
* def map = { a: 2, b: 4, c: 6 }
* karate.forEach(map, function(key, val) { karate.appendTo(keys, key); karate.appendTo(vals, val) })
* match keys == ['a', 'b', 'c']
* match vals == [2, 4, 6]
Loops and Data Generation
Generate test data using karate.repeat()
and karate.range()
.
Scenario: Generate data with karate.repeat
# Generate array with function
* def generateUser = function(i) { return { id: i, name: 'User' + (i + 1) } }
* def users = karate.repeat(3, generateUser)
* match users == [{ id: 0, name: 'User1' }, { id: 1, name: 'User2' }, { id: 2, name: 'User3' }]
# Generate numbers
* def double = function(i) { return i * 2 }
* def numbers = karate.repeat(5, double)
* match numbers == [0, 2, 4, 6, 8]
# Generate range of numbers
* def range = karate.range(4, 9)
* match range == [4, 5, 6, 7, 8, 9]
Use karate.repeat()
instead of JavaScript for
loops to keep code concise and readable.
Sorting and Deduplication
Scenario: Sort and deduplicate arrays
# Sort objects by nested property
* def items = [{ data: { priority: 3 } }, { data: { priority: 1 } }, { data: { priority: 2 } }]
* def sortFn = function(x) { return x.data.priority }
* def sorted = karate.sort(items, sortFn)
* match sorted[0].data.priority == 1
* match sorted[2].data.priority == 3
# Reverse sorted array
* def reversed = sorted.reverse()
* match reversed[0].data.priority == 3
# Remove duplicates from simple array
* def numbers = [1, 2, 2, 3, 3, 3, 4]
* def unique = karate.distinct(numbers)
* match unique == [1, 2, 3, 4]
Dynamic Validation
Apply validation rules conditionally based on response data.
Scenario: Conditional validation
* def validateResponse =
"""
function(response, context) {
var errors = [];
if (!response.id) errors.push('Missing id');
if (!response.status) errors.push('Missing status');
if (response.status === 'completed') {
if (!response.completedAt) errors.push('Missing completedAt');
} else if (response.status === 'failed') {
if (!response.error) errors.push('Missing error');
}
if (context.env === 'prod' && !response.signature) {
errors.push('Missing signature in production');
}
return errors;
}
"""
Given url 'https://api.example.com/operation'
When method get
Then status 200
* def errors = validateResponse(response, { env: karate.env })
* if (errors.length > 0) karate.fail('Validation errors: ' + errors.join(', '))
Reusable Utility Functions
Create reusable conditional patterns for common scenarios.
Scenario: Conditional utilities
# Choose first valid value
* def firstValid =
"""
function() {
for (var i = 0; i < arguments.length; i++) {
if (arguments[i] != null && arguments[i] !== undefined) {
return arguments[i];
}
}
return null;
}
"""
# Execute if value is present
* def executeIfPresent =
"""
function(value, action) {
if (value !== null && value !== undefined) {
return action(value);
}
return null;
}
"""
# Use utilities
* def endpoint = firstValid(
karate.properties['custom.endpoint'],
java.lang.System.getenv('API_ENDPOINT'),
'https://default.api.com'
)
* def token = executeIfPresent(response.token, function(t) { return 'Bearer ' + t })
* print 'Using endpoint:', endpoint
Store common utility functions in common/utils.feature
and call them with * def utils = call read('classpath:common/utils.feature')
for reusability across test suites.
Error Handling with Try-Catch
Handle errors gracefully with try-catch patterns.
Scenario: Safe feature execution
* def safeCall =
"""
function(feature, fallback) {
try {
return karate.call(feature);
} catch (e) {
karate.log('Error calling feature:', e.message);
if (fallback) {
return karate.call(fallback);
}
return { error: e.message };
}
}
"""
* def result = safeCall('primary-service.feature', 'fallback-service.feature')
* if (result.error) {
print 'Using cached data due to error:', result.error
* def data = read('classpath:cached-data.json')
} else {
* def data = result.response
}
JSON Lookup Pattern
Use JSON objects for data-driven conditional logic instead of switch statements.
Scenario: JSON lookup for branching
* def config =
"""
{
'zone1': { url: 'https://api1.example.com', timeout: 5000 },
'zone2': { url: 'https://api2.example.com', timeout: 10000 },
'zone3': { url: 'https://api3.example.com', timeout: 15000 }
}
"""
* def zone = 'zone2'
* def settings = config[zone]
* configure timeout = settings.timeout
Given url settings.url
When method get
Then status 200
JSON lookups are cleaner than switch statements for simple key-value branching.
Best Practices
Keep Conditions Simple
Extract complex logic into JavaScript functions for clarity.
# ✅ Good: Clear and simple
* if (response.status == 'active' && response.verified) karate.log('Valid user')
# ❌ Avoid: Complex nested conditions
* if ((a && b) || (c && !d) || (e && f && g)) karate.log('Something')
# ✅ Better: Extract into function
* def shouldProcess = function() { return (a && b) || (c && !d) || (e && f && g) }
* if (shouldProcess()) karate.log('Processing')
Use Descriptive Error Messages
Always provide context when using karate.fail()
.
# ✅ Good: Descriptive message
* if (!response.data) karate.fail('No data returned from API: ' + response.error)
# ❌ Avoid: Generic message
* if (!response.data) karate.fail('Test failed')
Handle Null Values Safely
Use optional chaining or explicit null checks to avoid errors.
# ✅ Good: Safe null handling
* def value = response.data?.user?.email || 'default@example.com'
* if (response.items?.length == 0) karate.log('No items')
# Explicit null check
* if (value !== null && value !== undefined) karate.log('Value exists')
- Use optional chaining (
?.
) for safe property access - Set
karate.env
in CI/CD pipelines and validate inkarate-config.js
- Prefer ES6 arrow functions (
x => x.name
) for concise transformations - Use
karate.repeat()
instead of JavaScriptfor
loops - Extract complex conditional logic into reusable JavaScript functions
Common Patterns Reference
Pattern | Use Case | Example |
---|---|---|
Inline if | Single statement execution | * if (status == 404) karate.abort() |
Ternary | Conditional assignment | * def url = env == 'prod' ? prodUrl : devUrl |
Eval block | Multi-line logic | * eval if (x) { ... } |
Switch function | Multiple conditions | function(x) { switch(x) { ... } } |
JSON lookup | Data-driven branching | * def config = {...}; def val = config[env] |
Optional chaining | Safe property access | response.data?.user?.email |
Next Steps
- Learn about lifecycle hooks: Hooks and Lifecycle
- Debug complex conditional logic: Debugging
- Set environment-specific configurations: Configuration
- Conditionally execute features: Calling Features