Skip to main content

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:

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:

Gherkin
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') |
Single-Cell Pattern

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:

Gherkin
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():

Gherkin
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 |
@setup Behavior
  • @setup scenarios 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
  • @setup scenarios still execute fully in dry-run mode so dynamic outlines resolve their rows — read karate.suite.dryRun inside an @setup to 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:

Gherkin
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:

Gherkin
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'):

Gherkin
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:

Gherkin
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:

Gherkin
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:

accounts.json
[
{ "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" }
]
driver.feature
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 |
provision.feature
@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.

Use embedded expressions, not <>, in step bodies

Outline 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:

Gherkin
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 CaseApproach
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