Skip to main content

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:

MarkerDescriptionExample
#ignoreSkip validation completely{ id: '#ignore' }
#nullMust be null (key must exist){ value: '#null' }
#notnullMust not be null{ id: '#notnull' }
#presentKey must exist (any value){ key: '#present' }
#notpresentKey must not exist{ key: '#notpresent' }
#arrayMust be JSON array{ items: '#array' }
#objectMust be JSON object{ data: '#object' }
#booleanMust be boolean{ active: '#boolean' }
#numberMust be number{ count: '#number' }
#stringMust be string{ name: '#string' }
#uuidMust be valid UUID{ id: '#uuid' }
#regex STRMust match regex{ type: '#regex [A-Z]+' }
#? EXPRCustom 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: