REUSABILITY
Calling Features
What You'll Learn
- Call and reuse feature files for modular test design
- Pass parameters to called features and extract results
- Use callonce for one-time setup and expensive operations
- Implement authentication flows and common test patterns
- Share variables between parent and called features
- Select specific scenarios using tag selectors
Overview
When you have a sequence of HTTP calls or test logic that needs to be repeated across multiple test scripts, Karate allows you to treat any *.feature
file as a reusable unit. You can pass parameters into the called feature and extract variables from the invocation result.
Basic Feature Calling
Simple Call
The call
keyword invokes another feature file, loaded using the read
function:
Feature: Calling another reusable feature
Background:
* def signIn = call read('classpath:my-signin.feature') { username: 'john', password: 'secret' }
* def authToken = signIn.authToken
Scenario: Main test scenario
* url apiUrl
* header Authorization = 'Bearer ' + authToken
* path '/protected-endpoint'
* method get
* status 200
```gherkin
### Called Feature Structure
Here's how the called feature `my-signin.feature` might look:
```gherkin
Feature: Sign-in functionality
Scenario:
Given url loginUrlBase
And request { userId: '#(username)', userPass: '#(password)' }
When method post
Then status 200
And def authToken = response.token
# Additional steps if needed
Given path 'users', authToken.userId, 'projects'
When method get
Then status 200
And def projects = response.projects
```gherkin
## Variable Scope and Return Values
### Passing Arguments
Only one JSON argument is allowed when calling a feature, but it can contain any complex structure:
```gherkin
# Simple arguments
* call read('create-user.feature') { name: 'John', age: 30 }
# Complex arguments
* def userData =
"""
{
name: 'John',
age: 30,
address: {
street: '123 Main St',
city: 'New York'
},
roles: ['user', 'admin']
}
"""
* call read('create-user.feature') userData
```gherkin
### Extracting Return Values
All variables defined in the called feature are returned as a JSON-like object:
```gherkin
# Call feature and capture results
* def result = call read('get-user-data.feature') { userId: 123 }
# Access returned variables
* def userName = result.userName
* def userEmail = result.userEmail
* def lastResponse = result.response # The last HTTP response
```gherkin
### Variable Isolation
- Karate creates a new context for the called feature
- Variables defined in the called feature don't overwrite parent variables
- JSON/XML/Map/List variables are passed by reference (can be mutated)
- Use `copy` keyword to pass a clone if needed
```gherkin
# Pass by reference (can be mutated)
* def data = { count: 0 }
* call read('increment.feature') { data: data }
* match data.count == 1
# Pass a copy (original unchanged)
* def original = { count: 0 }
* def dataCopy = copy original
* call read('increment.feature') { data: dataCopy }
* match original.count == 0
```gherkin
## Callonce for One-Time Setup
### Basic Callonce
The `callonce` keyword executes a feature only once and caches the result:
```gherkin
Feature: Test suite with expensive setup
Background:
# This executes only once, even with multiple scenarios
* def config = callonce read('expensive-setup.feature')
* def authToken = config.authToken
* def testData = config.testData
Scenario: Test 1
* print 'Using cached token:', authToken
Scenario: Test 2
* print 'Still using same token:', authToken
```gherkin
### When to Use Callonce
- Expensive initialization (database setup, test data creation)
- Authentication that can be reused across scenarios
- Loading configuration or test data
- Any operation you want to run once per feature
```gherkin
Background:
# One-time authentication
* def auth = callonce read('classpath:auth/get-token.feature')
* configure headers = { Authorization: '#("Bearer " + auth.token)' }
# One-time test data setup
* def testUsers = callonce read('classpath:data/create-test-users.feature')
```gherkin
## Shared Scope
### Without Variable Assignment
When `call` or `callonce` is used without assigning to a variable, the called feature shares and can update the parent scope:
```gherkin
# Shared scope - variables are merged into parent
* call read('setup-headers.feature')
# Now all variables from setup-headers.feature are available
# With assignment - isolated scope
* def result = call read('setup-headers.feature')
# Variables are contained in 'result' object
```gherkin
### Common Pattern for Authentication
```gherkin
Background:
# Shared scope pattern for auth setup
* callonce read('classpath:auth/login.feature')
# authToken variable is now directly available
* configure headers = { Authorization: '#("Bearer " + authToken)' }
Scenario: Use authenticated requests
* url apiUrl
* path '/protected'
* method get
* status 200
```gherkin
## Tag Selectors
### Calling Specific Scenarios
You can call specific scenarios within a feature using tag selectors:
```gherkin
# Call scenario by name
* call read('user-workflows.feature@name=createUser')
# Call scenario by tag
* call read('test-data.feature@setupData')
# Call within same feature
Scenario: Main test
* call read('@helperScenario')
@ignore @helperScenario
Scenario: Helper scenario
* print 'This is a helper'
```gherkin
### Multiple Scenarios in Called Feature
```gherkin
Feature: User workflows
@name=createUser
Scenario: Create a user
* print 'Creating user'
* def userId = java.util.UUID.randomUUID()
@name=deleteUser
Scenario: Delete a user
* print 'Deleting user:', userId
```gherkin
## Built-in Variables
### Special Variables in Called Features
| Variable | Description |
|----------|-------------|
| `__arg` | The complete argument object passed to the call |
| `__loop` | Current iteration index when called in a loop (starts from 0) |
```gherkin
# In calling feature
* def users = []
* def createUser = function(i){
return { name: 'User' + i, index: i }
}
* def users = karate.repeat(3, createUser)
* def results = call read('process-user.feature') users
# In process-user.feature
Scenario:
* print 'Processing user:', __arg.name
* print 'Loop index:', __loop
* def processedName = __arg.name + '_processed_' + __loop
```gherkin
## Default Values
Handle optional parameters with default values using `karate.get()`:
```gherkin
# In called feature - safe handling of optional parameters
Scenario:
# Set defaults if not provided
* def username = karate.get('username', 'defaultUser')
* def timeout = karate.get('timeout', 5000)
* def retries = karate.get('retries', 3)
* print 'Using username:', username
* print 'Timeout:', timeout
* print 'Retries:', retries
```gherkin
## Advanced Patterns
### Authentication Flow
```gherkin
# auth/login.feature
@ignore
Feature: Reusable authentication
Scenario:
* def credentials = karate.get('__arg', { username: 'default', password: 'default' })
* url authUrl
* path '/login'
* request credentials
* method post
* status 200
* def authToken = response.token
* def userId = response.userId
# Get user permissions
* path '/users', userId, '/permissions'
* header Authorization = 'Bearer ' + authToken
* method get
* status 200
* def permissions = response
```gherkin
### Feature as Function
Assign features to variables for custom keyword-like behavior:
```gherkin
Background:
* def createUser = read('classpath:helpers/create-user.feature')
* def deleteUser = read('classpath:helpers/delete-user.feature')
Scenario: User lifecycle test
* def user1 = call createUser { name: 'John', role: 'admin' }
* def user2 = call createUser { name: 'Jane', role: 'user' }
# Test with users
* print 'Created users:', user1.id, user2.id
# Cleanup
* call deleteUser { id: '#(user1.id)' }
* call deleteUser { id: '#(user2.id)' }
```gherkin
### Conditional Feature Calling
```gherkin
# Call feature based on condition
* if (environment == 'prod') call read('prod-setup.feature')
* if (needsAuth) def auth = call read('authenticate.feature')
# Call different features based on scenario
* def setupFeature = environment == 'prod' ? 'prod-setup.feature' : 'test-setup.feature'
* call read(setupFeature)
```gherkin
## Best Practices
### Modular Design
```gherkin
# ✅ Good: Single responsibility features
# auth/get-token.feature - Just gets token
# data/create-user.feature - Just creates user
# setup/config.feature - Just loads config
# ❌ Avoid: Monolithic features doing everything
# everything-setup.feature - Auth + data + config + more
```gherkin
### Parameter Documentation
```gherkin
# ✅ Good: Document expected parameters
Feature: Create user helper
# Expected parameters:
# - name: string (required)
# - email: string (optional, defaults to name@example.com)
# - role: string (optional, defaults to 'user')
@ignore
Scenario:
* def name = __arg.name
* def email = karate.get('__arg.email', name + '@example.com')
* def role = karate.get('__arg.role', 'user')
```gherkin
### Error Handling
```gherkin
# ✅ Good: Validate required parameters
Scenario:
* def userId = __arg.userId
* if (!userId) karate.fail('userId is required')
* url apiUrl
* path '/users', userId
* method get
* status 200
```gherkin
## Troubleshooting
### Common Issues
```gherkin
# Issue: Variable not found after call
# Solution: Ensure you're accessing via result object
* def result = call read('feature.feature')
* def value = result.variableName # Not just variableName
# Issue: Callonce not caching
# Solution: Ensure exact same argument
* callonce read('setup.feature') { env: 'test' } # First call
* callonce read('setup.feature') { env: 'test' } # Uses cache
* callonce read('setup.feature') { env: 'prod' } # New call (different arg)
# Issue: Shared scope overwriting variables
# Solution: Use explicit assignment
* def result = call read('feature.feature') # Isolated scope
```gherkin
## Next Steps
- Learn about [Data-Driven Tests](/docs/reusability/data-driven-tests) for batch processing
- Explore [Dynamic Scenarios](/docs/reusability/dynamic-scenarios) for flexible test flows
- Understand [Advanced Configuration](/docs/advanced/configuration) for complex setups