REUSABILITY
Data-Driven Tests
What You'll Learn
- Use Scenario Outline with Examples for data-driven testing
- Apply type hints and magic variables in data tables
- Load test data from external JSON, CSV, and other sources
- Create dynamic data-driven scenarios with runtime data
- Combine data tables with feature calling for batch testing
- Use embedded expressions and inline JSON in test data
Overview
Data-driven testing allows you to run the same test logic with multiple sets of input data, ensuring comprehensive coverage of different scenarios, boundary conditions, and edge cases. Karate provides powerful mechanisms for data-driven testing through Scenario Outlines, dynamic data sources, and feature calling with arrays.
Scenario Outline
Basic Examples Table
The Cucumber-style Scenario Outline
lets you define a template scenario that runs for each row in the Examples
table:
Feature: Data-driven API testing
Scenario Outline: Validate circuit by name
Given url 'https://api.example.com'
And path 'circuits/<name>.json'
When method get
Then status 200
And match response.location.country == '<country>'
Examples:
| name | country |
| monza | Italy |
| spa | Belgium |
| sepang | Malaysia |
```gherkin
### Named Scenarios with Placeholders
Scenario names can include placeholders for better reporting:
```gherkin
Scenario Outline: User <name> with age <age> validation
Given url apiUrl
And path 'users'
And request { name: '<name>', age: <age> }
When method post
Then status 201
And match response contains { name: '<name>', age: <age> }
Examples:
| name | age |
| Alice | 25 |
| Bob | 30 |
| Carol | 35 |
```gherkin
## Enhanced Features
### Type Hints
Add `!` to column headers to evaluate values as JavaScript types instead of strings:
```gherkin
Scenario Outline: Type-aware data testing
* match __row == { name: '#(name)', age: '#number', active: '#boolean' }
Examples:
| name | age! | active! |
| Alice | 25 | true |
| Bob | 30 | false |
| Carol | 35 | true |
```gherkin
### Magic Variables
Special variables available in Scenario Outlines:
| Variable | Description |
|----------|-------------|
| `__row` | The entire current row as a JSON object |
| `__num` | The current row index (starts at 0) |
```gherkin
Scenario Outline: Using magic variables
* print 'Processing row', __num, ':', __row
* def expectedIndex = __num
* match __row == { id: '#number', name: '#string' }
Examples:
| id! | name |
| 1 | Alice |
| 2 | Bob |
```gherkin
### Auto Variables
Each column automatically becomes a variable:
```gherkin
Scenario Outline: Auto variables from columns
# Direct variable access without <> brackets
* def fullName = firstName + ' ' + lastName
* match fullName == expectedName
Examples:
| firstName | lastName | expectedName |
| John | Doe | John Doe |
| Jane | Smith | Jane Smith |
```gherkin
### Inline JSON
Use complex JSON directly in Examples tables:
```gherkin
Scenario Outline: Complex data structures
* match user == expectedUser
* match address == expectedAddress
Examples:
| user! | address! |
| { "name": "John", "age": 30 } | { "city": "NYC", "zip": "10001" } |
| { "name": "Jane", "age": 25 } | { "city": "LA", "zip": "90001" } |
```gherkin
## Dynamic Data Sources
### JSON Array as Data Source
Load Examples data from external sources at runtime:
```gherkin
Scenario Outline: User validation: ${name}
Given url apiUrl
And path 'users'
And request { name: '#(name)', age: '#(age)' }
When method post
Then status 201
Examples:
| read('classpath:test-data/users.json') |
```gherkin
Where `users.json` contains:
```json
[
{ "name": "Alice", "age": 25 },
{ "name": "Bob", "age": 30 },
{ "name": "Carol", "age": 35 }
]
```gherkin
### CSV File Data Source
Load test data from CSV files:
```gherkin
Scenario Outline: CSV-driven testing
* print 'Testing user:', name, 'with role:', role
* call read('validate-user.feature') { name: '#(name)', role: '#(role)' }
Examples:
| read('classpath:test-data/users.csv') |
```gherkin
### Dynamic Data Generation
Generate test data programmatically:
```gherkin
Background:
* def generateTestData =
"""
function() {
var data = [];
for (var i = 1; i <= 5; i++) {
data.push({
id: i,
name: 'User' + i,
email: 'user' + i + '@example.com'
});
}
return data;
}
"""
Scenario Outline: Dynamic data: ${name}
* url apiUrl
* path 'users', id
* method get
* status 200
* match response.email == email
Examples:
| generateTestData() |
```gherkin
## Data-Driven Feature Calls
### Array-Driven Feature Calls
When calling a feature with a JSON array, it executes once per array element:
```gherkin
Feature: Batch user creation
Scenario: Create multiple users
* table users
| name | age | role |
| Alice | 25 | admin |
| Bob | 30 | user |
| Carol | 35 | manager |
* def results = call read('create-user.feature') users
* match each results[*].response == { id: '#number', status: 'created' }
```gherkin
### Processing Results
Extract and validate results from batch operations:
```gherkin
# Get all response bodies
* def responses = results[*].response
* match responses[*].status == ['created', 'created', 'created']
# Get specific fields
* def userIds = results[*].response.id
* match userIds == '#[3] #number'
# Validate all succeeded
* match each results == { response: { status: 'created' } }
```gherkin
## Advanced Patterns
### Conditional Data
Handle different test flows based on data:
```gherkin
Scenario Outline: Conditional validation
* if (userType == 'admin') karate.call('admin-setup.feature')
* url apiUrl
* path 'users', userId
* method get
* status 200
* if (userType == 'admin') match response.permissions contains 'write'
Examples:
| userId | userType |
| 1 | admin |
| 2 | user |
| 3 | guest |
```gherkin
### Null Values and Optional Fields
Empty cells become null values:
```gherkin
Scenario Outline: Optional field handling
* def payload = { name: name, email: email, phone: phone }
* remove payload.phone if (phone == null)
* url apiUrl
* path 'users'
* request payload
* method post
Examples:
| name | email | phone |
| Alice | alice@email.com | 555-0100 |
| Bob | bob@email.com | | # phone will be null
```gherkin
### Nested Data Structures
Work with complex nested data:
```gherkin
Scenario Outline: Complex nested validation
* def expected =
"""
{
user: {
name: '#(user.name)',
details: {
age: '#(user.details.age)',
city: '#(user.details.city)'
}
}
}
"""
* match response == expected
Examples:
| user! |
| { name: 'Alice', details: { age: 25, city: 'NYC' } } |
| { name: 'Bob', details: { age: 30, city: 'LA' } } |
```gherkin
## External Data Management
### JSON Data Files
Structure test data in JSON files:
```json
// test-data/api-tests.json
{
"validUsers": [
{ "id": 1, "expectedStatus": 200 },
{ "id": 2, "expectedStatus": 200 }
],
"invalidUsers": [
{ "id": 999, "expectedStatus": 404 },
{ "id": -1, "expectedStatus": 400 }
]
}
```gherkin
```gherkin
Background:
* def testData = read('classpath:test-data/api-tests.json')
Scenario Outline: Valid user tests
* call read('validate-user.feature') __row
Examples:
| testData.validUsers |
Scenario Outline: Invalid user tests
* call read('validate-user.feature') __row
Examples:
| testData.invalidUsers |
```gherkin
### Database as Data Source
Use Java interop to fetch test data from databases:
```gherkin
Background:
* def DbUtils = Java.type('com.example.DbUtils')
* def getTestUsers =
"""
function() {
return DbUtils.query("SELECT * FROM test_users WHERE active = true");
}
"""
Scenario Outline: Database-driven tests: ${username}
* url apiUrl
* path 'users', username
* method get
* status 200
Examples:
| getTestUsers() |
```gherkin
## Best Practices
### Data Organization
```gherkin
# ✅ Good: Logical grouping of test data
Examples:
# Valid cases
| id | status | description |
| 1 | 200 | existing user |
| 2 | 200 | another user |
# Error cases
| 99 | 404 | non-existent |
| -1 | 400 | invalid id |
# ❌ Avoid: Random unorganized data
Examples:
| id | status |
| 2 | 200 |
| 99 | 404 |
| 1 | 200 |
| -1 | 400 |
```gherkin
### Meaningful Test Names
```gherkin
# ✅ Good: Descriptive scenario names with context
Scenario Outline: Validate ${operation} for user type: ${userType}
# ❌ Avoid: Generic names
Scenario Outline: Test <id>
```gherkin
### Data Validation
```gherkin
# ✅ Good: Validate the data structure
Background:
* def testData = read('test-data.json')
* match each testData == { id: '#number', name: '#string' }
```gherkin
## Troubleshooting
### Common Issues
```gherkin
# Issue: Type mismatch in Examples
# Solution: Use type hints
Examples:
| age! | active! | # ! converts to number and boolean
| 25 | true |
# Issue: Dynamic data not loading
# Solution: Check file path and format
* def data = read('classpath:data/users.json') # Use classpath:
* print 'Loaded data:', data # Debug output
# Issue: Variable not found
# Solution: Check if using placeholder vs direct variable
* def name = '<name>' # String placeholder
* def name = name # Direct variable access
```gherkin
## Next Steps
- Learn about [Dynamic Scenarios](/docs/reusability/dynamic-scenarios) for runtime test generation
- Explore [Advanced Configuration](/docs/advanced/configuration) for complex setups
- Understand [Parallel Execution](/docs/running-tests/parallel-execution) for performance