Skip to main content

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:

HookWhen It RunsScopeUse Case
karate-config.jsBefore every ScenarioGlobalEnvironment setup, base configuration
BackgroundBefore every Scenario in FeatureFeatureCommon setup for all scenarios
callonce in BackgroundOnce per FeatureFeatureExpensive setup shared by scenarios
karate.callSingle()Once globallyGlobalOne-time initialization across all features
afterScenarioAfter every ScenarioScenarioCleanup, logging, screenshot on failure
afterFeatureAfter all Scenarios in FeatureFeatureFeature-level cleanup
Key Difference
  • callonce runs once per Feature
  • karate.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'
AfterFeature Hook Requirements

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 row
  • afterScenarioOutline runs after all rows complete

Configuration Files

karate-config.js Structure

Configure global settings and variables in karate-config.js:

src/test/java/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:

karate-config.js
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:

karate-config.js
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:

karate-config-dev.js
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:

karate-config.js
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;
}
Pure JSON Only

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:

karate-config.js
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:

karate-config.js
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