Skip to main content

ASSERTIONS

Match Keyword

Master Karate's most powerful assertion capability. The match keyword provides intelligent comparison for JSON, XML, and text with flexible validation patterns and data manipulation.

Basic Match Operations

Simple JSON Matching

Start with the fundamentals - compare JSON objects with exact equality:

Scenario: Simple JSON match
* def user = { id: 123, name: 'John Doe' }

# Exact match - order doesn't matter
* match user == { id: 123, name: 'John Doe' }
* match user == { name: 'John Doe', id: 123 }

# Property-specific matching
* match user.id == 123
* match user.name == 'John Doe'

The match keyword is smart - whitespace and key order don't matter. This makes assertions readable and maintainable.

Matching API Responses

Apply match to HTTP response validation:

Scenario: Validate API response
Given url 'https://api.example.com'
And path 'users', 123
When method get
Then status 200

# Match full response
And match response == { id: 123, name: 'John Doe', active: true }

# Match specific fields
And match response.id == 123
And match response.active == true

Match with Variables

Variables work on both sides of match expressions:

Scenario: Match with variables
* def expected = { id: 123, name: 'John Doe' }
* def actual = { name: 'John Doe', id: 123 }

# Variables can be compared
* match actual == expected

* def expectedId = 123
* match actual.id == expectedId

Match Operators

match != (Not Equals)

Use sparingly for simple negative assertions:

Scenario: Not equals validation
* def user = { id: 123, status: 'active' }

# Negative assertions
* match user != { id: 456 }
* match user.status != 'inactive'
* match user.id != 0

# Text not equals
* def message = 'Success'
* match message != 'Error'
* match message != ''
Best Practice

Prefer match == with fuzzy markers (#notnull, #notpresent) over != for complex validations. The != operator works best for simple string, number, or primitive comparisons.

Data Manipulation

Simple set Operations

Modify JSON objects by setting properties:

Scenario: Set properties on JSON
* def user = { id: 123, name: 'John' }

# Add new properties
* set user.active = true
* set user.role = 'admin'

# Modify nested properties
* set user.profile = { email: 'john@example.com' }
* set user.profile.verified = true

# Verify changes
* match user.active == true
* match user.profile.verified == true

set multiple - Bulk Assignment

Use tables to set multiple properties at once:

Scenario: Bulk property assignment
* def user = {}

# Table-based assignment
* set user
| path | value |
| name.first | 'John' |
| name.last | 'Doe' |
| age | 30 |
| profile.email | 'john@example.com' |

# Verify bulk assignment
* match user == {
name: { first: 'John', last: 'Doe' },
age: 30,
profile: { email: 'john@example.com' }
}

Non-existent keys are created automatically, making set perfect for building complex payloads.

remove - Delete Properties

Remove unwanted keys from JSON or XML:

Scenario: Remove JSON properties
* def user = {
id: 123,
name: 'John',
email: 'john@example.com',
password: 'secret'
}

# Remove sensitive fields
* remove user.password

# Verify removal
* match user == { id: 123, name: 'John', email: 'john@example.com' }
* match user.password == '#notpresent'

delete - Dynamic Property Removal

Use delete for dynamic paths:

Scenario: Dynamic property removal
* def key = 'tempData'
* def user = { id: 123, tempData: 'remove-me' }

# Delete using variable key
* delete user[key]

# Verify removal
* match user == { id: 123 }

Matching Sub-Sets

Basic contains

Check for subset matching without requiring exact equality:

Scenario: Object contains
* def user = {
id: 123,
name: 'John',
email: 'john@example.com',
role: 'admin'
}

# Subset matching - order doesn't matter
* match user contains { id: 123, name: 'John' }
* match user contains { role: 'admin' }

# Array contains
* def tags = ['admin', 'verified', 'premium']
* match tags contains 'admin'
* match tags contains ['admin', 'verified']

Not contains (!contains)

Verify elements are absent:

Scenario: Negative contains
* def user = { id: 123, name: 'John' }

# Verify keys don't exist
* match user !contains { password: '#present' }
* match user !contains { deleted: true }

# Array not contains
* def tags = ['admin', 'verified']
* match tags !contains 'suspended'
* match tags !contains 'deleted'

contains Variations

Use advanced contains operators for specific matching needs:

Scenario: Contains only - exact elements, any order
* def numbers = [1, 2, 3]

# All elements present, order doesn't matter
* match numbers contains only [3, 2, 1]
* match numbers contains only [2, 3, 1]

Scenario: Contains any - at least one element
* def tags = ['admin', 'verified', 'premium']

# At least one must be present
* match tags contains any ['admin', 'superuser']
* match tags contains any ['premium', 'enterprise']

Scenario: Contains deep - recursive matching
* def response = {
data: {
items: [{ product: { name: 'Laptop' } }]
}
}

# Deep nested matching
* match response contains deep {
data: { items: [{ product: { name: 'Laptop' } }] }
}

Match Each

Validate Array Elements

Apply schema validation to every element in an array:

Scenario: Validate each array element
* def users = [
{ id: 1, name: 'John', active: true },
{ id: 2, name: 'Jane', active: true },
{ id: 3, name: 'Bob', active: false }
]

# Each element must match schema
* match each users == {
id: '#number',
name: '#string',
active: '#boolean'
}

# Each element contains subset
* match each users contains { id: '#number' }

Cross-Field Validation

Use _$ to reference parent object in each iteration:

Scenario: Parent reference with _$
* def orders = [
{ items: [{ price: 100 }, { price: 200 }], total: 300 },
{ items: [{ price: 50 }], total: 50 }
]

# Validate total equals sum of item prices
* match each orders contains {
total: '#? _ == _$.items.reduce((s, i) => s + i.price, 0)'
}
SymbolEvaluates To
_Current value (self)
$JSON root
_$Parent object in match each

Fuzzy Matching

Type Validation Markers

Scenario: Type validation markers
* def user = {
id: 123,
name: 'John Doe',
email: 'john@example.com',
active: true,
uuid: 'a9f7a56b-8d5c-455c-9d13-808461d17b91'
}

# Type-based validation
* match user == {
id: '#number', # Must be a number
name: '#string', # Must be a string
email: '#regex .+@.+', # Must match pattern
active: '#boolean', # Must be boolean
uuid: '#uuid' # Must be valid UUID
}
MarkerDescription
#stringMust be a string
#numberMust be a number
#booleanMust be boolean (true/false)
#arrayMust be a JSON array
#objectMust be a JSON object
#nullMust be null (key must exist)
#notnullMust not be null
#presentKey must exist (any value, even null)
#notpresentKey must not exist
#ignoreSkip validation for this field
#uuidMust match UUID format
#regex STRMust match regex pattern

Optional Fields

Use ## prefix to mark fields as optional or nullable:

Scenario: Optional field validation
* def user = { name: 'John', role: 'admin' }

# Optional fields - can be missing or null
* match user == {
name: '#string', # Required
role: '#string', # Required
email: '##string', # Optional string
phone: '##string' # Optional string
}

Custom Validation with #?

Write custom validation logic using JavaScript expressions:

Scenario: Self-validation expressions
* def product = {
price: 99.99,
discount: 0.15,
quantity: 5
}

# Custom validation using #? and _ (self)
* match product == {
price: '#number? _ > 0 && _ < 10000', # Range check
discount: '#number? _ >= 0 && _ <= 1', # Percentage
quantity: '#number? _ > 0' # Positive number
}

# Custom validation functions
* def isValidPrice = function(p) { return p > 0 && p < 10000 }
* match product.price == '#? isValidPrice(_)'

Use _ for the current value, $ for JSON root, and _$ for parent in match each.

Advanced Patterns

JsonPath Queries

Extract and match using JsonPath expressions:

Scenario: JsonPath with wildcards
* def cat = {
name: 'Billie',
kittens: [
{ id: 23, name: 'Bob' },
{ id: 42, name: 'Wild' }
]
}

# Wildcard returns arrays
* match cat.kittens[*].id == [23, 42]
* match cat.kittens[*].name == ['Bob', 'Wild']

# JsonPath filters
* def bob = get[0] cat.kittens[?(@.id==23)]
* match bob.name == 'Bob'

JsonPath Short-Cuts

Use $variable short-cut for concise JsonPath expressions:

Scenario: $ short-cut form
* def cat = {
name: 'Billie',
kittens: [
{ id: 23, name: 'Bob' },
{ id: 42, name: 'Wild' }
]
}

# Short-cut form
* def names = $cat.kittens[*].name
* match names == ['Bob', 'Wild']

# Long form (equivalent)
* def names2 = get cat.kittens[*].name
* match names2 == ['Bob', 'Wild']

Text and Binary Matching

Match non-JSON responses:

Scenario: Text and binary responses
# Text matching
* def message = 'Health Check OK'
* match message == 'Health Check OK'
* match message contains 'OK'
* match message !contains 'Error'

# Binary matching (after HTTP request)
* match responseBytes == read('expected-file.pdf')

# Header matching (case-insensitive)
* match header Content-Type == 'application/json'
* match header Content-Type contains 'application'
Key Reminders
  • Prefer match over assert for better error messages
  • Use contains when order doesn't matter
  • Regex patterns need double backslash escaping: #regex a\\.dot
  • Symbol references: _ (self), $ (root), _$ (parent in each)

Next Steps