Skip to main content

ASSERTIONS

Match Keyword

Overview

The match keyword is Karate's most powerful assertion tool, providing intelligent comparison of JSON and XML structures. It ignores whitespace, handles dynamic fields, and supports flexible validation patterns.

Basic Match Operations

Exact Matching

# Simple value matching
* match response == 'Success'
* match statusCode == 200
* match isActive == true

# JSON object matching
* def expected = { name: 'John', age: 30 }
* match response == expected

# Array matching
* def expectedList = [1, 2, 3]
* match response == expectedList

JSON Comparison

# Order of keys doesn't matter
* def response = { age: 30, name: 'John' }
* match response == { name: 'John', age: 30 }

# Whitespace is ignored
* match response == {
name: 'John',
age: 30
}

# Nested object matching
* match response == {
user: {
id: 123,
profile: {
email: 'john@example.com'
}
}
}

XML Comparison

# XML matching
* def xml = <root><hello>world</hello><foo>bar</foo></root>
* match xml == <root><hello>world</hello><foo>bar</foo></root>

# XPath matching
* match xml/root/hello == 'world'
* match /root/foo == 'bar'

# Attribute matching
* def xml = <root><hello foo="bar">world</hello></root>
* match xml == <root><hello foo="bar">world</hello></root>

Fuzzy Matching Markers

Core Validation Markers

MarkerDescription
#ignoreSkip comparison for this field
#nullValue must be null
#notnullValue must not be null
#presentKey must exist (value can be anything)
#notpresentKey must not exist
#arrayValue must be an array
#objectValue must be an object
#booleanValue must be boolean
#numberValue must be a number
#stringValue must be a string
#uuidValue must be a valid UUID
#regex STRValue must match regex pattern
#? EXPRJavaScript expression must evaluate to true
#[NUM] EXPRArray validation with size/condition

Using Fuzzy Markers

# Ignore dynamic fields
* def response = { id: 'a9f7a56b-8d5c-455c-9d13-808461d17b91', name: 'John', timestamp: 1234567890 }
* match response == { id: '#uuid', name: '#string', timestamp: '#ignore' }

# Type validation
* match response == {
name: '#string',
age: '#number',
active: '#boolean',
tags: '#array',
profile: '#object'
}

# Regex patterns
* match response == {
email: '#regex .+@.+\\..+',
phone: '#regex \\d{3}-\\d{3}-\\d{4}',
code: '#regex [A-Z]{3}\\d{3}'
}

Null and Present Markers

# Distinguish between null and not present
* def foo = { a: null }
* match foo == { a: '#null' } # a exists and is null
* match foo == { a: '#present' } # a exists (any value)

* def bar = { }
* match bar == { a: '#notpresent' } # a doesn't exist
* match bar == { a: '##null' } # a is either null or not present

# Optional fields with ##
* def response = { name: 'John' }
* match response == { name: '#string', age: '##number' } # age is optional

Self-Validation Expressions

# Using #? for custom validation
* def response = { age: 25, score: 85 }
* match response == {
age: '#? _ >= 18 && _ <= 100',
score: '#? _ > 0 && _ <= 100'
}

# Complex validations
* def response = { price: 99.99, discount: 0.15 }
* match response == {
price: '#? _ > 0',
discount: '#? _ >= 0 && _ <= 1'
}

# Using variables in validation
* def minAge = 18
* match response == { age: '#? _ >= minAge' }

Match Contains

Partial Object Matching

# Check if object contains specific fields
* def response = { id: 1, name: 'John', age: 30, city: 'NYC' }
* match response contains { name: 'John', age: 30 }

# Nested contains
* def response = {
user: { id: 1, name: 'John', profile: { email: 'john@example.com' } }
}
* match response contains { user: { name: 'John' } }

# Multiple contains checks
* match response contains { id: '#number' }
* match response contains { name: '#string' }

Array Contains

# Check if array contains elements
* def response = [1, 2, 3, 4, 5]
* match response contains 3
* match response contains [2, 4]

# Array of objects
* def users = [
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' },
{ id: 3, name: 'Bob' }
]
* match users contains { id: 2, name: 'Jane' }
* match users contains [{ name: 'John' }, { name: 'Bob' }]

Not Contains

# Negative contains
* def response = { a: 1, b: 2 }
* match response !contains { c: '#notnull' }

* def array = [1, 2, 3]
* match array !contains 4
* match array !contains [5, 6]

Match Each

Array Element Validation

# Validate each element in array
* def response = [
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' },
{ id: 3, name: 'Bob' }
]
* match each response == { id: '#number', name: '#string' }

# Complex validation for each element
* match each response == {
id: '#? _ > 0',
name: '#regex [A-Za-z]+'
}

Conditional Each

# Different validations based on condition
* def items = [
{ type: 'book', price: 10.99 },
{ type: 'electronics', price: 299.99 }
]

* match each items == {
type: '#string',
price: '#? _ > 0'
}

# Validate subset
* match each items[?(@.type=='book')] == { price: '#? _ < 50' }

Advanced Match Patterns

Deep Matching

# Deep equality with contains
* def response = {
user: {
id: 1,
profile: {
name: 'John',
settings: {
theme: 'dark',
notifications: true
}
}
}
}

* match response contains deep {
user: {
profile: {
settings: {
theme: 'dark'
}
}
}
}

Cross-Field Validation

# Validate relationships between fields
* def response = { min: 10, max: 100, value: 50 }
* match response == {
min: '#number',
max: '#number',
value: '#? _ >= $.min && _ <= $.max'
}

# Date validation
* def response = { startDate: '2024-01-01', endDate: '2024-12-31' }
* match response == {
startDate: '#string',
endDate: '#? new Date(_) > new Date($.startDate)'
}

Schema Reuse

# Define reusable schema
* def userSchema = {
id: '#number',
name: '#string',
email: '#regex .+@.+',
age: '#? _ >= 0 && _ <= 120'
}

# Use in multiple places
* match response.user == userSchema
* match response.users[0] == userSchema
* match each response.users == userSchema

Match with Response

Response Validation

# Direct response matching
Given url apiUrl
When method get
Then match response == { status: 'success', data: '#array' }

# Using $ as response alias
Then match $ == { status: 'success' }
Then match $.data == '#array'
Then match $.data[0] == { id: '#number', name: '#string' }

Header Matching

# Match response headers
Then match header Content-Type == 'application/json'
Then match header X-Request-ID == '#uuid'

# Cookie matching
Then match responseCookies contains { sessionId: '#notnull' }
Then match responseCookies.sessionId.value == '#string'

Text and Binary Matching

Text Responses

# Plain text matching
Then match response == 'Success'
Then match response contains 'completed'
Then match response == '#regex .*success.*'

# Multi-line text
Then match response ==
"""
Line 1
Line 2
Line 3
"""

Binary Responses

# Binary comparison
Then match responseBytes == read('expected.pdf')

# Check binary size
* def size = responseBytes.length
* match size == '#? _ > 0'

Best Practices

Structured Validation

# ✅ Good: Clear, maintainable schema
* def expectedSchema = {
id: '#number',
status: '#? ["active", "pending", "inactive"].contains(_)',
created: '#string',
metadata: '#object'
}
* match response == expectedSchema

# ❌ Avoid: Inline complex validations
* match response == { id: '#number', status: '#? ["active", "pending", "inactive"].contains(_)', created: '#string', metadata: '#object' }

Appropriate Marker Usage

# ✅ Good: Use specific markers
* match response == { id: '#uuid', count: '#number' }

# ❌ Avoid: Too generic
* match response == { id: '#string', count: '#string' }

Error Messages

# ✅ Good: Validate with context
* match response contains { status: 'success' } || karate.fail('Expected success status')

# ✅ Good: Step-by-step validation for debugging
* match response.status == 'success'
* match response.data == '#array'
* match response.data.length > 0

Advanced Match Operations

Match Contains Deep

The match contains deep operation modifies the behavior of match contains to perform deep contains matching for nested objects and arrays. This is useful for complex nested payloads where you only want to validate specific values in the data structure.

# Deep contains matching for nested objects
* def original = { a: 1, b: 2, c: 3, d: { a: 1, b: 2 } }
* def expected = { a: 1, c: 3, d: { b: 2 } }
* match original contains deep expected

# Deep contains matching for nested arrays
* def original = { a: 1, arr: [ { b: 2, c: 3 }, { b: 3, c: 4 } ] }
* def expected = { a: 1, arr: [ { b: 2 }, { c: 4 } ] }
* match original contains deep expected

# Complex nested structure validation
* def response = {
user: {
id: 1,
profile: {
name: 'John',
settings: {
theme: 'dark',
notifications: true
}
}
}
}
* match response contains deep {
user: {
profile: {
settings: {
theme: 'dark'
}
}
}
}

Match Contains Only

The match contains only operation validates that all specified array elements are present but allows them to be in any order.

# Array order doesn't matter
* def data = { foo: [1, 2, 3] }
* match data.foo contains 1
* match data.foo contains [2]
* match data.foo contains [3, 2]
* match data.foo contains only [3, 2, 1]
* match data.foo contains only [2, 3, 1]

# This will fail - missing element
# * match data.foo contains only [2, 3]

# Object arrays with any order
* def users = [
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' },
{ id: 3, name: 'Bob' }
]
* match users contains only [
{ id: 3, name: 'Bob' },
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' }
]

Match Contains Only Deep

This combines the power of exact matching with order-independent array validation. All arrays at any depth are checked without considering order.

# Order doesn't matter for any arrays
* def response = { foo: [ 'a', 'b' ] }
* match response contains only deep { foo: [ 'b', 'a' ] }

# Complex nested arrays
* def response = {
categories: [
{ name: 'tech', items: [1, 2, 3] },
{ name: 'books', items: [4, 5, 6] }
]
}
* match response contains only deep {
categories: [
{ name: 'books', items: [6, 4, 5] },
{ name: 'tech', items: [3, 1, 2] }
]
}

Match Each Contains Deep

Combines match each with contains deep to perform deep contains matching on every element in an array.

# Validate essential keys in array elements
* def response = [
{
"a": 1,
"arr": [
{
"b": 2,
"c": 3
}
]
},
{
"a": 1,
"arr": [
{
"b": 2,
"c": 3
},
{
"b": 4,
"c": 5
}
]
}
]
* match each response contains deep { a: 1, arr: [ { b: 2 } ] }

# Validate API response collections
* def apiResponse = [
{
user: { id: 1, profile: { name: 'John' } },
permissions: ['read', 'write']
},
{
user: { id: 2, profile: { name: 'Jane' } },
permissions: ['read']
}
]
* match each apiResponse contains deep {
user: { profile: { name: '#string' } },
permissions: '#array'
}

Cross-Field Validation

Using $ for Document Root References

You can reference any JsonPath in the current document using $ to perform cross-field validations.

# Temperature conversion validation
* def temperature = { celsius: 100, fahrenheit: 212 }
* match temperature == {
celsius: '#number',
fahrenheit: '#? _ == $.celsius * 1.8 + 32'
}

# Range validation
* def response = { min: 10, max: 100, value: 50 }
* match response == {
min: '#number',
max: '#number',
value: '#? _ >= $.min && _ <= $.max'
}

# Date comparison
* def response = { startDate: '2024-01-01', endDate: '2024-12-31' }
* match response == {
startDate: '#string',
endDate: '#? new Date(_) > new Date($.startDate)'
}

# Array size validation against field
* def response = { count: 3, items: [1, 2, 3] }
* match response == {
count: '#number',
items: '#[$.count]' # Array size equals count field
}

Using _$ for Current Context in Match Each

The _$ symbol refers to the current item when using match each, enabling validation of relationships within array elements.

# Validate total price equals room price
* def json = {
"hotels": [
{ "roomInformation": [{ "roomPrice": 618.4 }], "totalPrice": 618.4 },
{ "roomInformation": [{ "roomPrice": 679.79}], "totalPrice": 679.79 }
]
}
* match each json.hotels contains {
totalPrice: '#? _ == _$.roomInformation[0].roomPrice'
}

# Alternative using embedded expressions
* match each json.hotels contains {
totalPrice: '#(_$.roomInformation[0].roomPrice)'
}

# Complex business rule validation
* def orders = [
{
items: [{ price: 10 }, { price: 20 }],
subtotal: 30,
tax: 3,
total: 33
},
{
items: [{ price: 15 }, { price: 25 }],
subtotal: 40,
tax: 4,
total: 44
}
]
* match each orders == {
items: '#array',
subtotal: '#? _ == _$.items.map(i => i.price).reduce((a,b) => a+b, 0)',
tax: '#? _ == _$.subtotal * 0.1',
total: '#? _ == _$.subtotal + _$.tax'
}

Reference Symbol Summary

SymbolContextDescription
$Any matchReferences the root of the current JSON document
_Self-validationReferences the current value being validated
_$Match eachReferences the current array item in match each operations
# Example showing all three symbols
* def data = {
config: { maxItems: 5 },
users: [
{ id: 1, items: [1, 2], count: 2 },
{ id: 2, items: [3, 4, 5], count: 3 }
]
}

* match each data.users == {
id: '#? _ > 0', # _ = current id value
items: '#? _.length <= $.config.maxItems', # $ = document root
count: '#? _ == _$.items.length' # _$ = current user object
}

Complex Predicate Expressions

Advanced Self-Validation

# Multiple condition validation
* def response = {
age: 25,
score: 85,
status: 'active',
tags: ['premium', 'verified']
}
* match response == {
age: '#? _ >= 18 && _ <= 100',
score: '#? _ > 0 && _ <= 100',
status: '#? ["active", "pending", "inactive"].includes(_)',
tags: '#? _.length > 0 && _.includes("verified")'
}

# Using variables in validation
* def minAge = 18
* def maxAge = 100
* def validStatuses = ['active', 'pending', 'inactive']
* match response == {
age: '#? _ >= minAge && _ <= maxAge',
status: '#? validStatuses.includes(_)'
}

# Complex object validation
* def response = {
user: {
email: 'john@example.com',
preferences: {
notifications: true,
theme: 'dark'
}
}
}
* match response == {
user: {
email: '#? _.includes("@") && _.includes(".")',
preferences: {
notifications: '#boolean',
theme: '#? ["light", "dark", "auto"].includes(_)'
}
}
}

Function-Based Validation

# Define reusable validation functions
* def isValidEmail = function(email) { return email.includes('@') && email.includes('.') }
* def isValidPhone = function(phone) { return /^\d{3}-\d{3}-\d{4}$/.test(phone) }
* def isAdult = function(age) { return age >= 18 }

# Use in validations
* def response = {
email: 'john@example.com',
phone: '555-123-4567',
age: 25
}
* match response == {
email: '#? isValidEmail(_)',
phone: '#? isValidPhone(_)',
age: '#? isAdult(_)'
}

# Complex array validation with functions
* def isValidUser = function(user) {
return user.id > 0 && user.name.length > 0 && user.email.includes('@')
}
* def users = [
{ id: 1, name: 'John', email: 'john@example.com' },
{ id: 2, name: 'Jane', email: 'jane@example.com' }
]
* match each users == '#? isValidUser(_)'

Next Steps