ASSERTIONS
Fuzzy Matching
Master Karate's flexible validation system using fuzzy matching markers. Handle dynamic data, optional fields, and complex validation scenarios with powerful pattern matching.
Ignore or Validate
Validation Marker Reference
Complete reference of all validation markers:
Marker | Description | Example |
---|---|---|
#ignore | Skip validation completely | { id: '#ignore' } |
#null | Must be null (key must exist) | { value: '#null' } |
#notnull | Must not be null | { id: '#notnull' } |
#present | Key must exist (any value) | { key: '#present' } |
#notpresent | Key must not exist | { key: '#notpresent' } |
#array | Must be JSON array | { items: '#array' } |
#object | Must be JSON object | { data: '#object' } |
#boolean | Must be boolean | { active: '#boolean' } |
#number | Must be number | { count: '#number' } |
#string | Must be string | { name: '#string' } |
#uuid | Must be valid UUID | { id: '#uuid' } |
#regex STR | Must match regex | { type: '#regex [A-Z]+' } |
#? EXPR | Custom validation | { age: '#? _ > 18' } |
Basic Ignore and Validate Patterns
Feature: Ignore and validate patterns
Scenario: Ignore dynamic fields
Given url baseUrl
And path 'users'
When method post
And request { name: 'John', email: 'john@test.com' }
Then status 201
# Ignore dynamic fields in response
And match response == {
id: '#ignore', # ID is auto-generated
name: 'John',
email: 'john@test.com',
created: '#ignore', # Timestamp is dynamic
uuid: '#ignore' # UUID is dynamic
}
Scenario: Validate with type checking
Given path 'api/data'
When method get
Then status 200
# Validate types without exact values
And match response == {
timestamp: '#string', # Must be string (any value)
count: '#number', # Must be number (any value)
active: '#boolean', # Must be boolean (any value)
items: '#array', # Must be array (any content)
metadata: '#object' # Must be object (any content)
}
Optional Fields
Double Hash (##) for Optional Fields
Feature: Optional field handling
Scenario: Optional fields with ##
Given url baseUrl
And path 'api/profiles'
When method get
Then status 200
# Mix required and optional fields
And match response == {
id: '#number', # Required field
name: '#string', # Required field
avatar: '##string', # Optional - can be missing or string
bio: '##string', # Optional - can be missing or string
website: '##null', # Optional - can be missing or null
settings: '##object' # Optional - can be missing or object
}
# Works with arrays too
And match response == {
tags: '##[] #string', # Optional array of strings
permissions: '##[]' # Optional array (any content)
}
Scenario: Optional vs nullable distinction * def testData = { required: 'value', nullable: null }
# Key exists with null value
* match testData == {
required: '#string',
nullable: '#null' # Key exists, value is null
}
# Key can be missing or null
* match testData == {
required: '#string',
nullable: '##null', # Can be missing or null
optional: '##string' # Can be missing completely
}
Remove If Null
Conditional Field Removal
Feature: Remove if null patterns
Scenario: Dynamic field inclusion
* def includeOptional = false
* def user = {
id: 123,
name: 'John',
email: 'john@test.com',
avatar: '#(includeOptional ? "avatar.jpg" : null)', # Removed if null
bio: '#(includeOptional ? "User bio" : null)' # Removed if null
}
# Fields with null values are removed
* match user == {
id: 123,
name: 'John',
email: 'john@test.com'
# avatar and bio are not present
}
Scenario: Conditional schema based on data
* def hasPermissions = true
* def hasProfile = false
* def dynamicUser = {
id: '#number',
name: '#string',
permissions: '#(hasPermissions ? "##[] #string" : null)', # Conditional schema
profile: '#(hasProfile ? "##object" : null)' # Conditional schema
}
Given url baseUrl
And path 'api/flexible-user'
When method get
Then status 200
# Validate with dynamic schema
And match response == dynamicUser
#null and #notpresent
Critical Distinction Between Null and Missing
Feature: Null vs not present validation
Scenario: Understand null vs missing
* def withNull = { id: 1, value: null }
* def withoutKey = { id: 1 }
# Key exists with null value
* match withNull == { id: 1, value: '#null' }
* match withNull == { id: 1, value: '#present' } # Also passes
# Key doesn't exist at all
* match withoutKey == { id: 1, value: '#notpresent' }
* match withoutKey !contains { value: '#present' }
# Common patterns
* match withNull != { value: '#notpresent' } # PASSES - key exists
* match withoutKey != { value: '#null' } # PASSES - key missing
Scenario: API response null handling
Given url baseUrl
And path 'api/user-with-nulls'
When method get
Then status 200
# Some APIs return nulls, others omit keys entirely
And match response == {
id: '#number',
name: '#string',
avatar: '##null', # Can be null or missing
bio: '##string', # Can be string or missing (not null)
lastLogin: '#null', # Must be null (key must exist)
preferences: '##object' # Can be object or missing
}
Match Text and Binary
Text and Binary Validation
Feature: Text and binary fuzzy matching
Scenario: Text response fuzzy matching
Given url baseUrl
And path 'api/messages'
When method get
Then status 200
# Text contains validation
And match response contains 'success'
And match response !contains 'error'
# Regex validation for text
* match response == '#regex .*operation completed.*'
# Text length validation
* match response == '#string? _.length > 10'
Scenario: Binary content validation
Given path 'files/image'
When method get
Then status 200
# Binary size validation
* def fileSize = responseBytes.length
* assert fileSize > 1000
# Binary content type validation
* match header Content-Type == '#regex image/.*'
# Compare against baseline
* match responseBytes == read('baseline-image.png')
Regex Escaping
Use double backslashes for regex special characters: '#regex a\.dot' matches 'a.dot'
match header and XML
Header Fuzzy Matching
Feature: Header and XML fuzzy matching
Scenario: Response header validation
Given url baseUrl
And path 'api/data'
When method get
Then status 200
# Header fuzzy matching (case-insensitive)
And match header Content-Type == 'application/json'
And match header Content-Type contains 'application'
And match header Content-Type == '#regex application/.*'
# Multiple header validation
And match header X-Rate-Limit-Remaining == '#number'
And match header X-Request-ID == '#uuid'
Scenario: XML fuzzy matching
Given path 'soap/data'
And request <getDataRequest><id>123</id></getDataRequest>
When soap action 'GetData'
Then status 200
# XML with ignored elements
And match response ==
"""
<dataResponse>
<id>123</id>
<timestamp>#ignore</timestamp>
<data>
<name>#string</name>
<value>#number</value>
<optional>#ignore</optional>
</data>
</dataResponse>
"""
# XML attributes with fuzzy matching
And match response ==
"""
<root>
<item id="#number" created="#ignore">
<name>#string</name>
</item>
</root>
"""
Matching Sub-Sets (Contains Shortcuts)
Contains Shortcut Operators
Feature: Contains shortcuts
Scenario: Shortcut operator usage * def expected = [
{ id: 1, name: 'Product A' },
{ id: 2, name: 'Product B' }
]
Given url baseUrl
And path 'api/products'
When method get
Then status 200
# Use contains shortcuts in embedded expressions
* match response == {
products: '#(^expected)', # contains (^)
featured: '#(^^expected)', # contains only (^^)
recommended: '#(^*expected)', # contains any (^*)
detailed: '#(^+expected)', # contains deep (^+)
excluded: '#(!^expected)' # not contains (!^)
}
Scenario: Complex shortcut combinations
* def userSubset = { id: '#number', name: '#string' }
* def requiredTags = ['verified', 'active']
Given path 'api/user-list'
When method get
Then status 200
And match response == {
users: '#(^userSubset)', # Each user contains id and name
tags: '#(^^requiredTags)', # Exactly these tags
categories: '#(^*["premium", "basic"])' # At least one of these
}
Advanced Fuzzy Patterns
Complex Validation Scenarios
Feature: Advanced fuzzy matching scenarios
Scenario: Multi-level optional validation
Given url baseUrl
And path 'api/complex-optional'
When method get
Then status 200
# Complex optional field patterns
And match response == {
user: {
id: '#number',
profile: '##object', # Optional profile object
settings: '##{ theme: "#string", lang: "##string" }' # Optional with nested optional
},
metadata: '##{ created: "#string", tags: "##[] #string" }',
features: '##[]' # Optional array
}
Scenario: Environment and user-based validation
* def env = karate.env || 'dev'
* def userType = 'premium'
Given path 'api/contextual-data'
And header X-User-Type = userType
When method get
Then status 200
# Validation that adapts to context
* def baseValidation = {
id: '#number',
name: '#string'
}
* def envValidation = env == 'dev' ?
{ debugData: '#object' } :
{ debugData: '#notpresent' }
* def userValidation = userType == 'premium' ?
{ premiumFeatures: '#[] #string' } :
{ premiumFeatures: '#notpresent' }
* def fullValidation = karate.merge(baseValidation, envValidation, userValidation)
* match response == fullValidation
Common Gotchas
- Optional marker position:
##
goes before the validation marker (##string
, not#string#
) - Null vs missing:
#null
requires key to exist,#notpresent
requires key to NOT exist - Regex escaping: Use double backslash (
#regex \\d+
) for literal dots and special chars - Performance impact: Complex fuzzy patterns with many
#?
expressions can slow tests - Case sensitivity: All validations are case-sensitive unless using custom functions
Next Steps
Master fuzzy matching and continue with:
- Match Keyword - Core match operations and data manipulation
- Schema Validation - Structured schema patterns
- HTTP Responses - Apply fuzzy matching to API responses