ADVANCED
Hooks and Lifecycle
Control test initialization, cleanup, and resource management using Karate's lifecycle hooks without requiring Java code.
Hook Types Overview
Karate provides lifecycle hooks without requiring Java code:
Hook | When It Runs | Scope | Use Case |
---|---|---|---|
karate-config.js | Before every Scenario | Global | Environment setup, base configuration |
Background | Before every Scenario in Feature | Feature | Common setup for all scenarios |
callonce in Background | Once per Feature | Feature | Expensive setup shared by scenarios |
karate.callSingle() | Once globally | Global | One-time initialization across all features |
afterScenario | After every Scenario | Scenario | Cleanup, logging, screenshot on failure |
afterFeature | After all Scenarios in Feature | Feature | Feature-level cleanup |
callonce
runs once per Featurekarate.callSingle()
runs once globally, even across parallel threads
Background Setup
Use Background
to run setup before every scenario in a feature:
Feature: User API
Background:
* url 'https://api.example.com'
* def authToken = 'Bearer abc123'
* header Authorization = authToken
Scenario: Get user
Given path 'users/1'
When method get
Then status 200
Scenario: Create user
Given path 'users'
And request { name: 'John Doe', email: 'john@example.com' }
When method post
Then status 201
The Background
section runs before each scenario, ensuring consistent setup.
One-Time Feature Setup
Use callonce
to execute expensive operations once per feature:
Feature: API Tests
Background:
# Runs once per feature, cached for all scenarios
* def authData = callonce read('classpath:auth/login.feature')
* def token = authData.token
Scenario: First test
* header Authorization = 'Bearer ' + token
* url 'https://api.example.com/users'
* method get
* status 200
Scenario: Second test
* header Authorization = 'Bearer ' + token
* url 'https://api.example.com/products'
* method get
* status 200
The callonce
executes once and caches the result for all scenarios in this feature.
After Scenario Hooks
Execute cleanup logic after each scenario completes:
Feature: After scenario cleanup
Background:
* configure afterScenario =
"""
function() {
karate.log('Scenario completed:', karate.scenario.name);
karate.log('Status:', karate.info.errorMessage ? 'FAILED' : 'PASSED');
}
"""
Scenario: Simple test
* def result = { value: 123 }
* match result.value == 123
Cleanup on Failure
Add conditional logic for failure handling:
Background:
* configure afterScenario =
"""
function() {
if (karate.info.errorMessage) {
karate.log('Test failed:', karate.info.errorMessage);
// Take screenshot or save debug logs
}
}
"""
Scenario: Test with failure handling
* def user = { id: 1, name: 'Alice' }
* match user.id == 1
After Feature Hook
Configure feature-level cleanup after all scenarios:
Feature: After feature cleanup
Background:
* configure afterFeature =
"""
function() {
karate.log('Feature completed');
// Cleanup feature-level resources
}
"""
Scenario: First scenario
* print 'Scenario 1'
Scenario: Second scenario
* print 'Scenario 2'
The afterFeature
hook only works with the Runner API for parallel execution, not the JUnit runner.
Won't work:
- ❌ JUnit
@Test
annotations - ❌ Single feature execution from IDE
Will work:
- ✅
Runner.path().parallel()
API - ✅ Command-line execution with parallel runner
Example:
// Required for afterFeature hooks
@Test
void testParallel() {
Results results = Runner.path("classpath:features")
.parallel(5);
}
See Parallel Execution for Runner API details.
After Scenario Outline
Handle data-driven test cleanup:
Background:
* configure afterScenario =
"""
function() {
karate.log('Row completed:', karate.scenario.name);
}
"""
* configure afterScenarioOutline =
"""
function() {
karate.log('All rows completed');
}
"""
Scenario Outline: Validate users
* def user = { id: <id>, name: '<name>' }
* match user.id == <id>
Examples:
| id | name |
| 1 | Alice |
| 2 | Bob |
| 3 | Carol |
afterScenario
runs after each Examples rowafterScenarioOutline
runs after all rows complete
Configuration Files
karate-config.js Structure
Configure global settings and variables in karate-config.js
:
function fn() {
var env = karate.env || 'dev';
var config = {
baseUrl: env == 'prod'
? 'https://api.prod.com'
: 'https://api.dev.com'
};
return config;
}
Variables returned in config
are available to all scenarios.
Global One-Time Setup
Use karate.callSingle()
for initialization that runs once globally:
function fn() {
var config = { baseUrl: 'https://api.example.com' };
// Runs once, even in parallel execution
var authToken = karate.callSingle('classpath:auth/get-token.feature');
config.token = authToken.response.token;
return config;
}
Multiple Calls with Cache Keys
Use different cache keys for different setups:
function fn() {
var config = {};
// Different cache keys for different auth types
var adminAuth = karate.callSingle('classpath:auth.feature?admin', {
username: 'admin',
password: 'admin123'
});
var userAuth = karate.callSingle('classpath:auth.feature?user', {
username: 'testuser',
password: 'user123'
});
config.adminToken = adminAuth.token;
config.userToken = userAuth.token;
return config;
}
The ?admin
and ?user
suffixes create separate cache entries.
Environment-Specific Configuration
Override base configuration with environment-specific files:
function fn() {
// Only used when karate.env=dev
var config = {
baseUrl: 'http://localhost:8080',
debugMode: true
};
return config;
}
Place karate-config-<env>.js
files alongside karate-config.js
to override settings based on the karate.env
system property.
Advanced Caching Strategies
Development Mode Token Caching
Cache authentication for faster development cycles:
function fn() {
var config = {};
// Enable caching in dev mode
if (karate.env === 'dev') {
karate.configure('callSingleCache', {
minutes: 15,
dir: 'target/auth-cache'
});
}
// This will be cached to disk in dev mode
var auth = karate.callSingle('classpath:expensive-auth.feature');
config.authHeader = 'Bearer ' + auth.token;
return config;
}
karate.callSingle()
requires pure JSON return values:
- ✅ Returns:
{ token: 'abc123', userId: 123 }
- ❌ Avoid:
{ token: 'abc123', connection: JavaObject }
Complex objects cause caching issues in parallel execution.
Parallel Execution and Thread Safety
Thread-Safe Global Setup
karate.callSingle()
is thread-safe for parallel execution:
function fn() {
var config = {};
// This runs only once even with parallel execution
var globalSetup = karate.callSingle('classpath:global-setup.feature', {
environment: karate.env,
timestamp: new Date().getTime()
});
// All parallel threads share this data
config.sharedData = globalSetup.response;
// Thread-local configuration
config.threadId = java.lang.Thread.currentThread().getName();
return config;
}
Thread-Safe State Management
Handle state in parallel execution using Java concurrency utilities:
function fn() {
var config = {};
// Thread-safe counter
var AtomicInteger = Java.type('java.util.concurrent.atomic.AtomicInteger');
var counter = new AtomicInteger(0);
config.getNextId = function() {
return counter.incrementAndGet();
};
return config;
}
Resource Management
Ensure proper cleanup after each scenario:
Feature: Resource cleanup
Background:
* def resources = []
* def createResource =
"""
function(type) {
var resource = { id: java.util.UUID.randomUUID(), type: type };
karate.get('resources').push(resource);
return resource;
}
"""
* configure afterScenario =
"""
function() {
var resources = karate.get('resources') || [];
resources.forEach(function(r) {
karate.log('Cleaning up:', r.type);
});
}
"""
Scenario: Test with automatic cleanup
* def user = createResource('user')
* def order = createResource('order')
* print 'Created resources:', user, order
Resources are tracked and cleaned up automatically after each scenario.
Next Steps
- Debug test failures: Debugging and Troubleshooting
- Run tests in parallel with hooks: Parallel Execution
- Manage environment variables: Variables and Data Types
- Share data across tests: Calling Features
- Configure test runners: JUnit Integration