ASSERTIONS
Match Keyword
The match keyword provides intelligent comparison for JSON, XML, and text with flexible validation patterns and data manipulation. It handles whitespace and key ordering automatically, producing clear error messages when assertions fail.
On this page:
- match == - Exact equality comparison
- match != - Not equals comparison
- match contains - Subset matching
- match each - Validate every array element
- Fuzzy markers - Type validation (
#string,#number,#uuid, etc.) - set / remove - Modify JSON and XML payloads
- JsonPath - Extract and match with path expressions
Basic Match Operations
Simple JSON Matching
Compare JSON objects with exact equality. Key order and whitespace don't matter:
Feature: Simple JSON matching
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'
Matching API Responses
Apply match to HTTP response validation:
Feature: API response matching
Scenario: Validate API response
Given url 'https://jsonplaceholder.typicode.com'
And path 'users', 1
When method get
Then status 200
# Match specific fields
* match response.id == 1
* match response.name == 'Leanne Graham'
* match response.email == 'Sincere@april.biz'
Match with Variables
Variables work on both sides of match expressions:
Feature: Match with variables
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
The left-hand side of match must be:
- A variable name:
foo - A named JsonPath/XPath:
foo.barorfoo[0].name - A function call:
foo.bar()orkarate.get('key') - An expression in parentheses:
(foo + bar)
match != (Not Equals)
Use for simple negative assertions:
Feature: Not equals matching
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. Use != for simple string or number comparisons.
Match Contains
JSON Object Contains
Check for subset matching without requiring exact equality:
Feature: Object contains matching
Scenario: Object contains
Given url 'https://jsonplaceholder.typicode.com'
And path 'users', 1
When method get
Then status 200
# Only check specific fields exist with expected values
* match response contains { id: 1, username: 'Bret' }
* match response contains { email: 'Sincere@april.biz' }
Array Contains
Check if arrays contain specific elements:
Feature: Array contains matching
Scenario: Array contains
* def tags = ['admin', 'verified', 'premium']
# Single element
* match tags contains 'admin'
# Multiple elements (order doesn't matter)
* match tags contains ['admin', 'verified']
* match tags contains ['verified', 'admin']
Not Contains (!contains)
Verify elements are absent:
Feature: Not contains matching
Scenario: Negative contains
* def user = { id: 123, name: 'John' }
# Verify keys don't have certain values
* match user !contains { id: 456 }
* match user !contains { deleted: true }
# Array not contains
* def tags = ['admin', 'verified']
* match tags !contains 'suspended'
* match tags !contains [4, 5]
contains only
Assert all elements are present in any order:
Feature: Contains only matching
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]
# This would fail - missing element 3
# * match numbers contains only [1, 2]
contains any
Assert at least one element is present:
Feature: Contains any matching
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 ['enterprise', 'premium']
# Works for objects too
* def data = { a: 1, b: 'x' }
* match data contains any { b: 'x', c: true }
contains deep
Recursive matching for nested structures:
Feature: Contains deep matching
Scenario: Contains deep - recursive matching
* def original = { a: 1, b: 2, c: 3, d: { a: 1, b: 2 } }
* def expected = { a: 1, c: 3, d: { b: 2 } }
# Deep nested matching - only checks specified keys
* match original contains deep expected
Scenario: Contains deep with 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
contains only deep
Like match == but array order doesn't matter at any depth:
Feature: Contains only deep matching
Scenario: Contains only deep
* def response = { foo: [ 'a', 'b', 'c' ] }
# Order doesn't matter at any depth
* match response contains only deep { foo: [ 'c', 'a', 'b' ] }
Match Each
Validate Array Elements
Apply validation to every element in an array:
Feature: Match each validation
Scenario: Validate each array element
Given url 'https://jsonplaceholder.typicode.com'
And path 'users'
When method get
Then status 200
# Each user must have these fields with correct types
* match each response == { id: '#number', name: '#string', email: '#string', username: '#string', phone: '#string', website: '#string', address: '#object', company: '#object' }
# Each element contains subset
* match each response contains { id: '#number', email: '#string' }
Cross-Field Validation
Use _$ to reference the parent object in each iteration:
Feature: Cross-field validation
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)' }
match each contains deep
Combine match each with contains deep for nested validation:
Feature: Match each contains deep
Scenario: Each contains deep
* def response =
"""
[
{ "a": 1, "arr": [ { "b": 2, "c": 3 } ] },
{ "a": 1, "arr": [ { "b": 2, "c": 3 }, { "b": 4, "c": 5 } ] }
]
"""
# Each item must deeply contain the expected structure
* match each response contains deep { a: 1, arr: [ { b: 2 } ] }
Symbol Reference
| Symbol | Evaluates To |
|---|---|
_ | Current value (self) |
$ | JSON root of the document |
_$ | Parent object in match each |
Fuzzy Matching
Type Validation Markers
Validate data types without checking exact values:
Feature: Type validation
Scenario: Type validation markers
Given url 'https://jsonplaceholder.typicode.com'
And path 'users', 1
When method get
Then status 200
# Type-based validation
* match response ==
"""
{
id: '#number',
name: '#string',
username: '#string',
email: '#string',
address: '#object',
phone: '#string',
website: '#string',
company: '#object'
}
"""
Complete Marker Reference
| Marker | Description |
|---|---|
#ignore | Skip validation for this field |
#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 |
#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 |
#uuid | Must match UUID format |
#regex STR | Must match regex pattern |
#? EXPR | JavaScript expression must return true |
#[NUM] | Array must have NUM elements |
#[] EXPR | Array validation with optional schema |
#(EXPR) | Embedded expression (substituted before match) |
Regex patterns need double backslash escaping: #regex a\\.dot matches a.dot
Optional Fields (##)
Use ## prefix to mark fields as optional or nullable:
Feature: Optional field validation
Scenario: Optional field validation
* def user = { name: 'John', role: 'admin' }
# Optional fields - can be missing or null
* match user == { name: '#string', role: '#string', email: '##string', phone: '##string' }
Scenario: Optional null handling
# ##null matches both missing keys AND keys with null values
* def foo = { a: 1 }
* match foo == { a: 1, b: '##null' }
* def bar = { a: 1, b: null }
* match bar == { a: 1, b: '##null' }
#null vs #notpresent
Karate distinguishes between null values and missing keys:
Feature: Null vs not present
Scenario: Null vs not present
# Key missing entirely
* def foo = { }
* match foo == { a: '##null' }
* match foo == { a: '#notpresent' }
# Key exists with null value
* def bar = { a: null }
* match bar == { a: '#null' }
* match bar == { a: '#present' }
# Key exists with value
* def baz = { a: 1 }
* match baz == { a: '#notnull' }
* match baz == { a: '#present' }
Custom Validation with #?
Write custom validation logic using JavaScript expressions:
Feature: Custom validation
Scenario: Self-validation expressions
* def product = { price: 99, discount: 15, quantity: 5 }
# Custom validation using #? and _ (self)
* match product == { price: '#number? _ > 0 && _ < 10000', discount: '#number? _ >= 0 && _ <= 100', quantity: '#number? _ > 0' }
Scenario: Validation with external variables
* def min = 1
* def max = 100
* def value = { count: 50 }
* match value == { count: '#? _ >= min && _ <= max' }
Scenario: Custom validation functions
* def isValidEmail = function(e) { return e.indexOf('@') > 0 }
* def user = { email: 'john@example.com' }
* match user.email == '#? isValidEmail(_)'
Cross-Field Validation with $
Reference the JSON root using $ for cross-field validation:
Feature: Cross-field validation
Scenario: Cross-field validation
* def temperature = { celsius: 100, fahrenheit: 212 }
# Validate fahrenheit based on celsius value
* match temperature == { celsius: '#number', fahrenheit: '#? _ == $.celsius * 1.8 + 32' }
# Using embedded expression (cleaner for equality checks)
* match temperature contains { fahrenheit: '#($.celsius * 1.8 + 32)' }
Array Length Validation
Use #[NUM] shortcut for array size validation:
Feature: Array length validation
Scenario: Array length validation
* def items = ['a', 'b', 'c']
# Check array type
* match items == '#[]'
# Check exact length
* match items == '#[3]'
# Check length with element schema
* match items == '#[3] #string'
# Validate each element with predicate
* match items == '#[]? _.length == 1'
Data Manipulation
set - Add or Modify Properties
Modify JSON objects by setting properties:
Feature: Set properties
Scenario: Set properties on JSON
* def user = { id: 123, name: 'John' }
# Add new properties
* set user.active = true
* set user.role = 'admin'
# Create nested properties (auto-creates path)
* set user.profile.email = 'john@example.com'
* set user.profile.verified = true
# Verify changes
* match user.active == true
* match user.profile.email == 'john@example.com'
set multiple - Bulk Assignment
Use tables to set multiple properties at once:
Feature: Bulk property assignment
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' } }
Create arrays with multiple columns:
Feature: Create array with set
Scenario: Create array with set
* set users
| path | 0 | 1 |
| name | 'John' | 'Jane' |
| role | 'admin' | 'user' |
* match users == [{ name: 'John', role: 'admin' }, { name: 'Jane', role: 'user' }]
remove - Delete Properties
Remove keys from JSON or XML:
Feature: Remove properties
Scenario: Remove JSON properties
* def user = { id: 123, name: 'John', password: 'secret', temp: 'data' }
# Remove single property
* remove user.password
# Remove using JsonPath
* remove user $.temp
# Verify removal
* match user == { id: 123, name: 'John' }
* match user.password == '#notpresent'
Remove array elements by index:
Feature: Remove array elements
Scenario: Remove array elements
* def items = [1, 2, 3, 4, 5]
# Remove by index
* remove items[1]
* match items == [1, 3, 4, 5]
# Remove using JsonPath
* remove items $[0]
* match items == [3, 4, 5]
remove - XML Elements
Remove works for XML as well:
Feature: Remove XML elements
Scenario: Remove XML elements
* def xml = <root><item>keep</item><temp>remove</temp></root>
# Remove XML element
* remove xml /root/temp
* match xml == <root><item>keep</item></root>
delete - Dynamic Property Removal
Use delete for dynamic paths with variable keys:
Feature: Dynamic property removal
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 }
JsonPath Queries
Wildcards and Filters
Extract data using JsonPath expressions:
Feature: JsonPath wildcards
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']
# Deep scan with .. operator (matches at any depth)
* match cat..name == ['Billie', 'Bob', 'Wild']
# JsonPath filter
* def bob = get[0] cat.kittens[?(@.id==23)]
* match bob.name == 'Bob'
get Keyword
Extract values using the get keyword:
Feature: Get keyword
Scenario: Extract with get
* def cat =
"""
{
"name": "Billie",
"kittens": [
{ "id": 23, "name": "Bob" },
{ "id": 42, "name": "Wild" }
]
}
"""
# Get array of names
* def names = get cat.kittens[*].name
* match names == ['Bob', 'Wild']
# Get first matching element
* def firstKitten = get[0] cat.kittens
* match firstKitten.id == 23
JsonPath on Variables
Use $varName for JsonPath on variables other than response:
Feature: JsonPath on variables
Scenario: JsonPath on variables
* def data = { users: [{ name: 'John' }, { name: 'Jane' }] }
# Short-cut form for variables
* def names = $data.users[*].name
* match names == ['John', 'Jane']
# Equivalent long form
* def names2 = get data.users[*].name
* match names2 == ['John', 'Jane']
In JsonPath expressions, bare $ is a shortcut for the response variable only. For other variables, use $varName.path form.
XML Matching
All fuzzy markers work with XML:
Feature: XML matching
Scenario: XML fuzzy matching
* def xml = <root><hello>world</hello><id>123</id></root>
# Fuzzy markers in XML
* match xml == <root><hello>#string</hello><id>#ignore</id></root>
Scenario: XML attribute matching
* def xml = <root><item id="123" status="active">content</item></root>
# Match with attribute validation
* match xml == <root><item id="#string" status="#ignore">content</item></root>
Text and Binary Matching
Match non-JSON responses:
Feature: Text and binary matching
Scenario: Text matching
* def message = 'Health Check OK'
* match message == 'Health Check OK'
* match message contains 'OK'
* match message !contains 'Error'
match header
Shortcut for response header validation (case-insensitive):
Feature: Header matching
Scenario: Header matching
Given url 'https://jsonplaceholder.typicode.com'
And path 'users', 1
When method get
Then status 200
# Case-insensitive header matching
* match header Content-Type contains 'application/json'
Next Steps
- Learn schema validation patterns: Schema Validation
- Explore fuzzy matching techniques: Fuzzy Matching
- Understand response handling: Response Handling