Skip to main content

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