Skip to main content

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
Best Practice

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')
Key Points
  • Use optional chaining (?.) for safe property access
  • Set karate.env in CI/CD pipelines and validate in karate-config.js
  • Prefer ES6 arrow functions (x => x.name) for concise transformations
  • Use karate.repeat() instead of JavaScript for loops
  • Extract complex conditional logic into reusable JavaScript functions

Common Patterns Reference

PatternUse CaseExample
Inline ifSingle statement execution* if (status == 404) karate.abort()
TernaryConditional assignment* def url = env == 'prod' ? prodUrl : devUrl
Eval blockMulti-line logic* eval if (x) { ... }
Switch functionMultiple conditionsfunction(x) { switch(x) { ... } }
JSON lookupData-driven branching* def config = {...}; def val = config[env]
Optional chainingSafe property accessresponse.data?.user?.email

Next Steps