REUSABILITY
Dynamic Scenarios
Generate test scenarios dynamically at runtime using JSON arrays, CSV files, or custom generator functions. Dynamic Scenario Outline resolves test data at execution time from files, databases, or APIs—enabling scalable data-driven testing without memory overhead.
On this page:
- JSON Data Source - Load test data from JSON files
- CSV Data Source - Use CSV for tabular test data
- @setup Tag - Generate data programmatically
- Generator Functions - Memory-efficient data generation
- Advanced Patterns - Multiple setups and external sources
Simple JSON Data Source
Load test data from a JSON file at runtime. The key pattern is a single-cell Examples table that triggers dynamic resolution:
Feature: Dynamic data from JSON
Scenario Outline: Create user: <name>
Given url 'https://jsonplaceholder.typicode.com'
And path 'users'
And request { name: '<name>', email: '<email>' }
When method post
Then status 201
# Single-cell table triggers dynamic resolution
Examples:
| read('classpath:test-data/users.json') |
Dynamic Scenario Outline requires exactly one row and one column in the Examples table. This signals Karate to resolve the data at runtime instead of using a static table.
CSV File Data Source
Use CSV files for tabular test data. Karate auto-converts CSV to JSON arrays:
Feature: CSV-driven tests
Scenario Outline: Test product: <name>
Given url 'https://jsonplaceholder.typicode.com'
And path 'posts'
And request { title: '<name>', body: '<description>', userId: 1 }
When method post
Then status 201
Examples:
| read('classpath:test-data/products.csv') |
@setup Tag for Data Generation
Generate test data programmatically before running scenarios. The @setup tag marks a scenario that runs first and provides data via karate.setup():
Feature: Setup tag for dynamic data
@setup
Scenario: Generate test data
* def testPosts =
"""
[
{ title: 'First Post', body: 'Content 1', userId: 1 },
{ title: 'Second Post', body: 'Content 2', userId: 1 },
{ title: 'Third Post', body: 'Content 3', userId: 2 }
]
"""
Scenario Outline: Create post: <title>
Given url 'https://jsonplaceholder.typicode.com'
And path 'posts'
And request { title: '<title>', body: '<body>', userId: <userId> }
When method post
Then status 201
And match response.title == '<title>'
Examples:
| karate.setup().testPosts |
@setupscenarios run before Background and all other scenarios- Cannot depend on Background variables
- Access setup data using
karate.setup()in the Examples table - Use
karate.setupOnce()to run setup only once per feature @setupscenarios still execute fully in dry-run mode so dynamic outlines resolve their rows — readkarate.suite.dryRuninside an@setupto short-circuit expensive fixture creation when needed
Generator Functions
Simple Generator
Create large datasets without loading all data into memory. A generator function returns null to signal the end:
Feature: Memory-efficient generator
@setup
Scenario: Setup generator function
* def postGenerator =
"""
function(index) {
if (index >= 10) return null;
return {
title: 'Post ' + index,
body: 'Body content for post ' + index,
userId: (index % 3) + 1
};
}
"""
Scenario Outline: Create post: <title>
Given url 'https://jsonplaceholder.typicode.com'
And path 'posts'
And request { title: '<title>', body: '<body>', userId: <userId> }
When method post
Then status 201
Examples:
| karate.setup().postGenerator |
Generator with Conditional Logic
Add computed fields or conditional values based on the index. The generator can include any logic to vary test data across iterations:
Feature: Generator with conditions
@setup
Scenario: Complex generator
* def generator =
"""
function(index) {
if (index >= 20) return null;
return {
title: 'TestPost' + index,
userId: (index % 5) + 1,
shouldSucceed: index % 4 != 0
};
}
"""
Scenario Outline: Post test <index> - <title>
Given url 'https://jsonplaceholder.typicode.com'
And path 'posts'
And request { title: '<title>', userId: <userId> }
When method post
Then status 201
Examples:
| karate.setup().generator |
Advanced Patterns
Named Setup Scenarios
When a feature has multiple Scenario Outlines that need different data sources, use named setups with @setup=name syntax. Reference specific setups using karate.setup('name'):
Feature: Named setup scenarios
@setup=users
Scenario: Setup user data
* def users =
"""
[
{ id: 1, name: 'Admin' },
{ id: 2, name: 'User' }
]
"""
@setup=posts
Scenario: Setup post data
* def posts =
"""
[
{ id: 101, title: 'First' },
{ id: 102, title: 'Second' }
]
"""
Scenario Outline: Test user: <name>
Given url 'https://jsonplaceholder.typicode.com'
And path 'users', <id>
When method get
Then status 200
Examples:
| karate.setup('users').users |
Setup Once Pattern
When multiple Scenario Outlines share the same data source, use karate.setupOnce() to execute the setup scenario only once. The data is cached and reused across all outlines:
Feature: Setup once pattern
@setup
Scenario: Generate shared data
* def sharedData =
"""
[
{ postId: 1, commentBody: 'Great post!' },
{ postId: 1, commentBody: 'Thanks for sharing' }
]
"""
Scenario Outline: Add comment to post <postId>
Given url 'https://jsonplaceholder.typicode.com'
And path 'comments'
And request { postId: <postId>, body: '<commentBody>', email: 'test@test.com' }
When method post
Then status 201
Examples:
| karate.setupOnce().sharedData |
Building Test Matrix
Generate all combinations of multiple variables using nested loops. Useful for testing features across user types, environments, or configurations:
Feature: Test matrix generation
@setup
Scenario: Create test matrix
* def users = [1, 2, 3]
* def postTypes = ['text', 'image']
* def testMatrix = []
* karate.forEach(users, function(userId) { karate.forEach(postTypes, function(type) { testMatrix.push({ userId: userId, postType: type, title: type + ' post by user ' + userId }) }) })
Scenario Outline: Create <postType> post for user <userId>
Given url 'https://jsonplaceholder.typicode.com'
And path 'posts'
And request { title: '<title>', body: 'Content', userId: <userId> }
When method post
Then status 201
Examples:
| karate.setup().testMatrix |
Running Against Multiple Configurations
A common need: run the same set of API feature files against many account / tenant / market configurations, where each configuration first needs a one-time setup call to an external service (provision an account, mint a session token, etc.). The dynamic outline pattern handles this natively — one Suite, one report, parallel-ready, no JUnit @TestFactory plumbing:
[
{ "name": "alpha-us", "preset": "premium", "market": "US" },
{ "name": "beta-uk", "preset": "basic", "market": "UK" },
{ "name": "gamma-jp", "preset": "premium", "market": "JP" },
{ "name": "delta-de", "preset": "basic", "market": "DE" }
]
Feature: Run the same API features against many account configurations
@setup
Scenario:
* def data = read('accounts.json')
Scenario Outline: account <name> (preset=<preset>, market=<market>)
# 1. Provision this account via the external auth service.
* def provisioned = call read('provision.feature') { preset: '#(preset)', market: '#(market)' }
* def account = provisioned.account
# 2. Exercise the API features with this account's token.
* call read('users.feature') { account: '#(account)' }
* call read('orders.feature') { account: '#(account)' }
Examples:
| karate.setup().data |
@ignore
Feature: Mint a session for one account
Scenario:
* url authUrl
* path 'auth', 'provision'
* request { preset: '#(preset)', market: '#(market)' }
* method post
* status 200
* def account = response
Each outline row runs as its own scenario, so the four accounts run in parallel with the suite's normal parallelism — there's no need to launch separate Karate suites or override system properties per configuration. Each row provisions its own session and isolates its variables; tokens cannot bleed between rows.
<>, in step bodiesOutline columns (preset, market, …) are bound as proper JavaScript variables in scope. Inside JSON, use #(preset); in match expressions and other JS contexts, use the bare identifier (match account.preset == preset). The <preset> form is only needed in the Scenario Outline: title line (where it interpolates into the scenario name shown in reports).
Database-Driven Tests
Query test data directly from a database using Java interop. The setup scenario executes the query and returns rows as a JSON array for the Scenario Outline:
Feature: Database-driven tests
@setup
Scenario: Load from database
* def DbUtils = Java.type('com.mycompany.DbUtils')
* def dbConfig = { url: 'jdbc:h2:mem:test', user: 'sa' }
* def query = 'SELECT user_id, username FROM test_users WHERE active = true'
* def testUsers = DbUtils.readRows(dbConfig, query)
Scenario Outline: Test user - <username>
Given url baseUrl
And path 'user/profile'
And request { userId: <user_id> }
When method get
Then status 200
Examples:
| karate.setup().testUsers |
When to Use Dynamic Scenarios
| Use Case | Approach |
|---|---|
| Fixed, small dataset (< 20 rows) | Static Examples table |
| Runtime data (API, database, files) | Dynamic Scenario Outline |
| Large datasets (100+ test cases) | Generator functions |
| Environment-specific test generation | @setup with conditional logic |
Next Steps
- Compare data-driven approaches: Data-Driven Tests
- Reuse test logic across features: Calling Features
- Add conditional test logic: Conditional Logic
- Explore parallel test execution: Parallel Execution