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 != ''
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)'
}
Symbol | Evaluates 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
}
Marker | Description |
---|---|
#string | Must be a string |
#number | Must be a number |
#boolean | Must be boolean (true/false) |
#array | Must be a JSON array |
#object | Must be a JSON object |
#null | Must be null (key must exist) |
#notnull | Must not be null |
#present | Key must exist (any value, even null) |
#notpresent | Key must not exist |
#ignore | Skip validation for this field |
#uuid | Must match UUID format |
#regex STR | Must 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'
- Prefer
match
overassert
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
- Explore schema validation patterns: Schema Validation
- Learn about response validation: Response Validation
- Understand data-driven testing: Data-Driven Tests