REUSABILITY
Calling Features
Reuse authentication flows, test data setup, and common operations by calling feature files and JavaScript functions. Variables and configuration from the caller are automatically available in called features.
On this page:
- call - Execute a feature file and get results
- callonce - Execute once per feature, cache results
- karate.callSingle() - Execute once globally across all tests
- Shared scope - Merge variables into caller's scope
- JavaScript functions - Define and call reusable functions
- Java interop - Call Java code from Karate
Calling Feature Files
Use call read() to execute another feature file and capture results:
Feature: Basic feature calling
Background:
* def signIn = call read('classpath:auth/login.feature') { username: 'john', password: 'secret' }
* def authToken = signIn.authToken
Scenario: Use authentication token
Given url 'https://jsonplaceholder.typicode.com'
And path 'users', 1
And header Authorization = 'Bearer ' + authToken
When method get
Then status 200
The called feature returns all variables defined with def as keys in a JSON-like object.
Called Feature Example
Here's what the called feature looks like:
@ignore
Feature: Login helper
Scenario:
Given url 'https://httpbin.org'
And path 'post'
And request { userId: '#(username)', userPass: '#(password)' }
When method post
Then status 200
* def authToken = response.json
- All variables and
configuresettings are passed to called features - You cannot use
* url 'http://...'and expect it in the called feature - use a variable instead:* url myUrl - Use
configure urlor pass the URL as a parameter for dynamic URL handling
Passing Parameters
Pass data using a single JSON argument. The keys in the JSON become variables in the called feature:
Feature: Passing parameters to features
Scenario: Pass credentials via variable
* def credentials = { username: 'admin', password: 'secret123' }
* def authResult = call read('classpath:auth/login.feature') credentials
* print authResult.authToken
Scenario: Pass credentials inline
* def authResult = call read('classpath:auth/login.feature') { username: 'john', password: 'secret' }
* print authResult.authToken
@ignore
Feature: Login helper
Scenario:
# username and password are available as variables from the JSON argument
Given url loginUrlBase
And request { userId: '#(username)', userPass: '#(password)' }
When method post
Then status 200
* def authToken = response
The JSON argument keys (username, password) become variables that can be used with embedded expressions #(variableName) in the called feature.
Default Values in Called Features
Use karate.get() to provide defaults for optional parameters:
Feature: User setup with defaults
Scenario:
# Provide defaults if parameters not passed
* def username = karate.get('username', 'testuser')
* def role = karate.get('role', 'user')
* def retries = karate.get('retries', 3)
Given url 'https://jsonplaceholder.typicode.com'
And path 'users'
And request { username: '#(username)', role: '#(role)' }
When method post
Then status 201
Built-in Variables
Access call arguments and loop state using built-in variables:
| Variable | Description |
|---|---|
__arg | The entire argument object passed to call (null if none). Typically not needed since JSON keys become variables directly. |
__loop | Current iteration index when called with array (starts at 0, -1 if not in loop) |
Data-Driven Calling
When you pass a JSON array, the feature is called once per element:
Feature: Data-driven feature calling
Scenario: Create multiple users
* table users
| name | role |
| 'John' | 'admin' |
| 'Jane' | 'user' |
| 'Bob' | 'guest' |
* def results = call read('create-user.feature') users
# results is an array of 3 result objects
* match results == '#[3]'
* def responses = $results[*].response
* match each responses contains { id: '#number' }
@ignore
Feature: Create single user
Scenario:
# name and role are directly available as variables from each array element
# __loop contains the current iteration index (0, 1, 2, ...)
Given url 'https://jsonplaceholder.typicode.com'
And path 'users'
And request { name: '#(name)', role: '#(role)' }
When method post
Then status 201
Tag Selectors
Call specific scenarios by tag:
Feature: Tag selectors
Scenario: Call by tag
# Call scenarios with @setup tag
* call read('classpath:common.feature@setup')
# Call scenario by name
* call read('classpath:workflows.feature@name=createUser')
Call Same Feature
Call another scenario within the same feature file using just the tag:
Feature: Same-file calling
Scenario: Main test
# No assignment = shared scope, so 'result' merges into this scenario
* call read('@helper')
* match result == 'from helper'
@ignore @helper
Scenario: Helper scenario
* def result = 'from helper'
The copy Keyword
Objects are passed by reference. Use copy to clone data and prevent mutation:
Feature: Copy keyword example
Scenario: Prevent mutation
* def originalData = { id: 1, items: ['a', 'b'] }
# Create a clone using 'copy' keyword
* copy clonedData = originalData
# Pass the clone - original won't be modified by called feature
* def result = call read('modify-data.feature') clonedData
# Original unchanged even if called feature mutated the data
* match originalData.items == ['a', 'b']
Shared Scope
When call is used without assigning to a variable, variables from the called feature merge into the caller's scope:
Feature: Shared scope example
Background:
# No assignment - variables merge into this feature's scope
* call read('classpath:common-setup.feature') { env: 'test' }
# authToken, baseUrl, etc. now available here
Scenario: Use shared variables
Given url baseUrl
And path 'users', 1
And header Authorization = 'Bearer ' + authToken
When method get
Then status 200
@ignore
Feature: Common setup
Scenario:
* def baseUrl = 'https://jsonplaceholder.typicode.com'
* def authToken = 'test-token-123'
* configure headers = { 'X-Custom': 'value' }
- Authentication setup that sets headers for all requests
- Common configuration that multiple scenarios need
- One-liner setup calls in Background
Use with care - called features can overwrite existing variables.
Isolated vs Shared Scope
| Pattern | Scope | Use Case |
|---|---|---|
def result = call read('f.feature') | Isolated | Get result, don't affect caller variables |
call read('f.feature') | Shared | Merge all variables into caller |
karate.call('f.feature') | Isolated | JS API form, same as first pattern |
karate.call(true, 'f.feature') | Shared | JS API with shared scope |
callonce
Execute a feature only once per feature file, caching results for all scenarios:
Feature: One-time setup
Background:
# Runs only once, even with multiple scenarios
* def authData = callonce read('classpath:auth/expensive-login.feature')
* def token = authData.token
Scenario: First test
Given url 'https://jsonplaceholder.typicode.com'
And path 'users'
And header Authorization = 'Bearer ' + token
When method get
Then status 200
Scenario: Second test
# authData and token still available - no re-execution
Given url 'https://jsonplaceholder.typicode.com'
And path 'posts'
And header Authorization = 'Bearer ' + token
When method get
Then status 200
Use callonce for:
- Authentication token generation
- Database initialization
- Test data creation
- Environment setup
callonce should return pure JSON data. Avoid returning JavaScript functions or complex Java objects - they may cause issues in parallel execution.
karate.callSingle()
Execute a feature once globally across all test files, even in parallel execution. Use in karate-config.js:
function fn() {
var env = karate.env || 'dev';
// Runs once across ALL features
var auth = karate.callSingle('classpath:auth/global-login.feature', { env: env });
return {
baseUrl: auth.baseUrl,
authToken: auth.token
};
}
Feature: Using global authentication
Scenario: Use globally cached auth
# authToken available from karate-config.js
Given url baseUrl
And path 'users', 1
And header Authorization = 'Bearer ' + authToken
When method get
Then status 200
Multiple callSingle Calls
The first argument is used as the cache key. To call the same feature with different arguments, add a ?name suffix:
// Different cache keys for different users
var adminAuth = karate.callSingle('classpath:auth/login.feature?admin', { username: 'admin', password: 'admin123' });
var userAuth = karate.callSingle('classpath:auth/login.feature?user', { username: 'user', password: 'user456' });
Dev Mode Caching
Cache callSingle results to disk during development to avoid repeated auth calls:
function fn() {
if (karate.env == 'local') {
// Cache for 15 minutes
karate.configure('callSingleCache', { minutes: 15 });
}
var auth = karate.callSingle('classpath:auth/login.feature');
return { token: auth.token };
}
Return only pure JSON data (strings, numbers, objects, arrays). Do not return:
- JavaScript functions
- Java objects
- Complex objects with circular references
These can cause issues in parallel execution due to caching and serialization.
JavaScript Functions
Define reusable JavaScript functions:
Feature: JavaScript functions
Background:
* def calculateTotal =
"""
function(items) {
var sum = 0;
for (var i = 0; i < items.length; i++) {
sum += items[i].price * items[i].quantity;
}
return { subtotal: sum, tax: sum * 0.08, total: sum * 1.08 };
}
"""
Scenario: Use function
* def items = [{ price: 100, quantity: 2 }, { price: 50, quantity: 1 }]
* def totals = call calculateTotal items
* match totals.subtotal == 250
Direct Function Calls
Call functions directly with multiple arguments (no call keyword needed):
Feature: Direct function calls
Scenario: Call functions with multiple arguments
* def multiply = function(a, b) { return a * b }
* def formatName = function(first, last) { return first + ' ' + last }
* def result = multiply(5, 10)
* match result == 50
* def fullName = formatName('John', 'Doe')
* match fullName == 'John Doe'
Function Return Behavior
When a function returns a map-like object:
Feature: Function return behavior
Scenario: Return value behavior
* def getConfig = function() { return { host: 'jsonplaceholder.typicode.com', port: 443 } }
# Assigned - variables stay in result object
* def config = call getConfig
* match config.host == 'jsonplaceholder.typicode.com'
# Not assigned - keys become variables in current scope
* call getConfig
* match host == 'jsonplaceholder.typicode.com'
* match port == 443
Java Interop
Call Java code from Karate:
Feature: Java interop
Scenario: Call static Java method
* def Base64 = Java.type('java.util.Base64')
* def encoded = Base64.getEncoder().encodeToString('hello'.getBytes())
* match encoded == 'aGVsbG8='
Scenario: Call instance method
* def ArrayList = Java.type('java.util.ArrayList')
* def list = new ArrayList()
* list.add('item1')
* list.add('item2')
* match list.size() == 2
Wrapping Java in Functions
For cleaner reuse, wrap Java calls in JavaScript functions:
Feature: Wrapping Java in functions
Background:
* def uuid =
"""
function() {
var UUID = Java.type('java.util.UUID');
return UUID.randomUUID().toString();
}
"""
Scenario: Generate UUID
* def id = uuid()
* match id == '#uuid'
HTTP Basic Auth Example
Combine Java and JavaScript for authentication:
function fn(creds) {
var temp = creds.username + ':' + creds.password;
var Base64 = Java.type('java.util.Base64');
var encoded = Base64.getEncoder().encodeToString(temp.toString().getBytes());
return 'Basic ' + encoded;
}
Feature: HTTP Basic Auth
Scenario: Use basic auth
* def basicAuth = read('classpath:basic-auth.js')
* header Authorization = call basicAuth { username: 'john', password: 'secret' }
Given url 'https://httpbin.org'
And path 'get'
When method get
Then status 200
call vs read() Reference
| Code | Description |
|---|---|
def result = call read('f.feature') | Isolated scope, get result |
call read('f.feature') | Shared scope, merge variables |
call read('f.feature') arg | Pass argument to feature |
def f = read('f.feature') then call f | Store feature, call later |
karate.call('f.feature') | JS API, isolated scope |
karate.call(true, 'f.feature') | JS API, shared scope |
def data = read('data.json') | Load JSON data (not executed) |
def fn = read('utils.js') | Load JS file |
Next Steps
- Combine with data tables: Data-Driven Tests
- Generate dynamic scenarios: Dynamic Scenarios
- Learn about hooks: Hooks