Skip to main content

REUSABILITY

Data-Driven Tests

Run the same test logic with multiple data sets using Scenario Outline or Karate's dynamic feature calling—no code duplication required.

Benefits of Data-Driven Testing

  • Zero code duplication: Write test logic once, run with many variations
  • Maintainable: Update test data separately from test logic
  • Comprehensive coverage: Test edge cases without bloating test files
  • Clear reporting: Each data set appears as separate test result
  • Flexible data sources: Use inline tables, CSV, YAML, JSON, or external APIs

Simple Table Data

Create inline test data using the table keyword to define JSON arrays directly in your tests.

Feature: Simple table data

Scenario: Create test data with table
* table users
| name | age |
| 'Alice' | 30 |
| 'Bob' | 25 |
| 'Carol' | 35 |

* match users == '#[3]'
* match users[0] == { name: 'Alice', age: 30 }
* match users[1].name == 'Bob'
Table Syntax

String values must be quoted: 'Alice' not Alice. Numbers and booleans do not need quotes: 30, true, false.

Basic Scenario Outline

Run the same scenario multiple times with different inputs using Scenario Outline and Examples tables.

Feature: Basic Scenario Outline

Scenario Outline: Validate user registration
Given url baseUrl
And path 'users'
And request { name: '<name>', email: '<email>' }
When method post
Then status <expectedStatus>

Examples:
| name | email | expectedStatus |
| Alice | alice@example.com | 201 |
| Bob | bob@example.com | 201 |
| | missing@test.com | 400 |
| Carol | invalid-email | 400 |

The Cucumber Way

Traditional Scenario Outline

Use Scenario Outline for fixed data sets with readable, self-documenting tables.

Feature: Cucumber-style data-driven tests

Scenario Outline: API validation with multiple inputs
Given url baseUrl
And path 'api/validate'
And request { username: '<username>', role: '<role>' }
When method post
Then status <status>
And match response.access == <hasAccess>

Examples:
| username | role | status | hasAccess |
| admin | admin | 200 | true |
| john | user | 200 | true |
| guest | guest | 403 | false |
| invalid | none | 401 | false |

Type Hints and Enhancements

Add ! suffix to column headers to evaluate values as JavaScript expressions instead of strings.

Feature: Type hints with ! suffix

Scenario Outline: Complex data types
* def testData = { count: <count>, active: <active>, tags: <tags> }
* match testData.count == '#number'
* match testData.active == '#boolean'
* match testData.tags == '#array'

Examples:
| count! | active! | tags! |
| 5 | true | ['a', 'b', 'c'] |
| 10 | false | ['x', 'y'] |
| 0 | true | [] |

The Karate Way

Dynamic Feature Calling

Call reusable features with array data for flexible, programmatic data-driven testing.

Feature: Karate-style dynamic testing

Scenario: Test multiple users dynamically
* def testUsers = [
{ name: 'Alice', role: 'admin', expectSuccess: true },
{ name: 'Bob', role: 'user', expectSuccess: true },
{ name: 'Guest', role: 'guest', expectSuccess: false }
]

* def results = call read('validate-access.feature') testUsers

* def successful = results.filter(r => r.success)
* match successful == '#[2]'
* print 'Tested', testUsers.length, 'users'

Arrays as Data Sources

Generate test data programmatically or load from variables for maximum flexibility.

Feature: Array-based data sources

Scenario: Generate dynamic test data
* def userCount = 5
* def generateUser = function(i) { return { id: i, name: 'User' + i } }
* def users = karate.repeat(userCount, generateUser)

* def results = call read('test-user.feature') users
* match results == '#[5]'
* match results[*].responseStatus == [201, 201, 201, 201, 201]

File-Based Data Sources

Load test data from external files to separate data from test logic.

CSV Data

Feature: CSV data sources

Scenario: Load users from CSV file
* def users = read('test-users.csv')
* match users == '#[]'
* print 'Loaded', users.length, 'users from CSV'

* def results = call read('create-user.feature') users
* match results[*].responseStatus contains only 201

YAML Data

Feature: YAML data sources

Scenario: Load structured test data
* def testData = read('test-scenarios.yaml')
* match testData.scenarios == '#[]'

* def results = call read('run-scenario.feature') testData.scenarios
* def failures = results.filter(r => r.failed)
* assert failures.length == 0

JSON Data

Feature: JSON data sources

Scenario Outline: Use JSON file in Examples
Given url baseUrl
And path 'api/process'
And request { type: '<type>', value: '<value>' }
When method post
Then status <expectedStatus>

Examples:
| read('test-cases.json') |

Advanced Scenario Outline

Magic Variables

Karate provides built-in variables __row and __num for accessing row data and index.

Feature: Magic variables

Scenario Outline: Access row data and index
* print 'Testing row', __num, 'with data:', __row
* match __row.name == '<name>'
* match __row.age == <age>
* match __num >= 0

Given url baseUrl
And path 'users'
And request __row
When method post
Then status 201

Examples:
| name | age! | email |
| Alice | 30 | alice@example.com |
| Bob | 25 | bob@example.com |
| Carol | 35 | carol@example.com |

Dynamic Scenario Names

Use placeholders in Scenario Outline names for better test reporting.

Feature: Dynamic scenario names

Scenario Outline: Validate <name> with role <role>
* print 'Testing user:', '<name>', 'role:', '<role>'

Given url baseUrl
And path 'access/check'
And request { username: '<name>', role: '<role>' }
When method post
Then status <expectedStatus>

Examples:
| name | role | expectedStatus |
| admin | admin | 200 |
| john | user | 200 |
| guest | guest | 403 |

Dynamic Data Generation

Programmatic Test Data

Generate test data with JavaScript functions for complex scenarios.

Feature: Programmatic data generation

Scenario: Generate test combinations
* def roles = ['admin', 'user', 'guest']
* def regions = ['US', 'EU', 'APAC']

* def testCases = []
* karate.forEach(roles, function(role) { karate.forEach(regions, function(region) { testCases.push({ role: role, region: region }) }) })

* match testCases == '#[9]'
* def results = call read('test-access.feature') testCases
* print 'Tested', testCases.length, 'combinations'

API-Driven Data

Load test data from external APIs for testing against live data sets.

Feature: API-driven test data

Scenario: Use API response as test data
Given url baseUrl
And path 'admin/active-users'
And header Authorization = 'Bearer ' + adminToken
When method get
Then status 200

* def users = response.users
* print 'Testing', users.length, 'active users'

* def results = call read('validate-user.feature') users
* def failures = results.filter(r => r.status != 200)
* assert failures.length == 0

Common Patterns

Testing Multiple Environments

Test the same logic across different environments efficiently.

Feature: Multi-environment testing

Scenario: Health check across all environments
* def environments = ['dev', 'qa', 'staging']
* def buildConfig = function(env) { return { env: env, url: karate.get('config.' + env + '.url') } }
* def configs = environments.map(buildConfig)

* def results = call read('health-check.feature') configs
* match results[*].responseStatus == [200, 200, 200]
* print 'All environments healthy'

Boundary Testing

Test edge cases and boundaries systematically with data tables.

Feature: Boundary value testing

Scenario Outline: Test age validation boundaries
Given url baseUrl
And path 'users/validate'
And request { name: 'Test', age: <age> }
When method post
Then status <expectedStatus>

Examples:
| age! | expectedStatus | description |
| -1 | 400 | # Negative |
| 0 | 400 | # Zero |
| 17 | 400 | # Under minimum |
| 18 | 200 | # Minimum valid |
| 120 | 200 | # Maximum valid |
| 121 | 400 | # Over maximum |

When to Use Each Approach

Choose the right data-driven approach based on your testing needs:

ScenarioUse ThisWhy
Fixed test cases (5-20 rows)Scenario OutlineMore readable, self-documenting tables
Large data sets (100+ rows)Karate WayBetter performance and memory usage
Dynamic conditionsKarate WayCan filter, transform, generate data
Simple validation matrixScenario OutlineTables are familiar to stakeholders
Cross-environment testingKarate WayCan load environment-specific data
Boundary testingScenario OutlineClear visibility of edge cases
Large Data Sets

For 100+ test cases, use Karate Way with call read() instead of Scenario Outline. Better performance and avoids memory issues with large Examples tables.

Next Steps