In Rest Assured you import Hamcrest (equalTo, hasItem, containsString) and chain it with JsonPath to assert on responses. Karate replaces all of that with a single keyword: match. It handles exact equality, partial matching, type checking, regex, array membership, and recursive schema validation — all in one readable syntax. This lesson is the complete guide to match.
The match keyword
match is always followed by an expression and then a value to compare it against:
# Exact equality
And match response.name == 'Alice'
And match response.id == 42
And match response.active == true
# Not equal
And match response.name != 'Bob'
And match response.status != 'deleted'match is not assert. The difference: match gives you Karate's rich comparison engine with clear, structured failure messages. assert is a raw JavaScript boolean expression — use it only when match doesn't cover the case.
match contains — partial object matching
When you care about some fields but not all, match contains is the right tool:
# Response may have more fields — we only care that name is 'Alice'
And match response contains { name: 'Alice' }
# Array contains at least one matching element
And match response.users contains { role: 'admin' }Compare with match ==, which is an exact, full equality check:
# This FAILS if the response has any field not listed here
And match response == { id: 1, name: 'Alice' }Use match == when you own the entire response contract. Use match contains when the response has fields you don't need to assert on — common for large response objects where you want to verify a subset.
Fuzzy matchers — assert the type, not the value
Fuzzy markers let you validate that a field is the right type without knowing its exact value. This is essential for dynamic values like auto-generated IDs, timestamps, and UUIDs:
And match response == {
id: '#number',
name: '#string',
active: '#boolean',
tags: '#array',
address: '#object',
deletedAt: '#null',
phone: '##string',
createdAt: '#notnull'
}Karate fuzzy markers — the full set
| Marker | Meaning | Example | |
|---|---|---|---|
| Type markers | #string #number #boolean #array #object | Value must be the named JSON type | match response.id == '#number' |
| Presence markers | #present #notnull | #present: key exists (even if null). #notnull: exists and is not null | match response.id == '#notnull' |
| Optionals | ##string ##number ##boolean | Double # = optional — the field can be that type OR null/absent | match response.phone == '##string' |
| Regex & custom | #uuid #regex pattern #? expression | #uuid: valid UUID. #regex: regex match. #?: custom JS boolean expression | match response.id == '#uuid' match response.role == '#? _ == "admin" || _ == "tester"' |
| Array size | #[n] #[_ > 0] | Array size — exact count or expression. _ is the array length | match response.users == '#[10]' match response.tags == '#[_ > 0]' |
match each — assert on every element in an array
When a response returns a list, match each applies the same assertion to every element:
# Every user must have a numeric id and a string name
And match each response == { id: '#number', name: '#string', email: '#string' }
# Every user's role must be one of three values
And match each response contains { role: '#? _ == "admin" || _ == "tester" || _ == "viewer"' }In Rest Assured the equivalent uses everyItem(...) with Hamcrest matchers inside a GPath expression. Karate's match each is more readable and handles nested objects naturally.
contains only and contains any
# All fields present, no extra fields, order doesn't matter
And match response contains only { id: '#number', name: '#string', email: '#string' }
# At least one element matches
And match response.tags contains any ['java', 'python', 'js']contains only is stricter than contains — the response can't have extra fields. contains any is an OR: the collection only needs to include one of the listed values.
A complete assertion example
Scenario: Validate user response in detail
Given path 'users', 1
When method get
Then status 200
And match response == {
id: '#number',
name: '#string',
email: '#regex .+@.+\\..+',
username: '#string',
phone: '##string',
website: '##string',
address: {
street: '#string',
city: '#string',
zipcode: '#string'
},
company: '#object'
}One match block validates the entire response shape. The ##string markers allow optional fields. The #regex marker validates the email format without knowing the actual address. The nested address object is validated inline — no separate assertion needed.
⚠️ Common mistakes
- Using
match ==for large objects and then fighting exact-match failures. If the API response has 20 fields and you only care about 3,match ==requires you to specify all 20 or the assertion fails. Usematch containsfor partial validation — it's the correct tool for "I care about these fields, I don't care about the rest." - Forgetting that
#?expressions use_for the current value.#? _ > 0means "this number is greater than zero." The underscore is the placeholder — it's not a Python_or a JavaScript discard. Writing#? value > 0instead of#? _ > 0silently fails becausevalueis undefined. - Using JavaScript
assertor TestNG assertions instead ofmatch. You can write* assert response.status == 'active'and it works, but the failure message is a raw JavaScript assertion error with no context.match response.status == 'active'fails with "match failed: response.status — actual: inactive, expected: active." Prefermatcheverywhere.
🎯 Practice task
Write assertions that use every major match variant. 30–40 minutes.
- GET
/users/1from JSONPlaceholder. Writematch response ==with fuzzy markers for every field — use#numberforid,#stringforname,##stringforphoneandwebsite(they may be present or absent). Run green. - Write
match response contains { name: '#string', email: '#regex .+@.+' }. Note it passes even though the response has more fields. Changecontainsto==and observe the failure — the error shows what extra fields caused the mismatch. Restore. - GET
/users(the full list). Writematch each response == { id: '#number', name: '#string', email: '#string', username: '#string' }. This asserts that every user in the list has those four fields with the right types. Run green. - Add
match response == '#[10]'to the same scenario — assert there are exactly 10 users. Run green. - Write a scenario that POSTs a new user and then uses
match response.id == '#notnull'andmatch response.id == '#number'to assert the returned id. Confirm both pass. - Regex marker: GET
/users/1and addmatch response.email == '#regex .+@.+\\..+'. Then write a failing version:match response.email == '#regex \\d+'(digits only). Read the failure message — note Karate shows the actual value and the pattern that failed. - Stretch: write a scenario using
#?for a custom validation. Assert thatresponse.id > 0usingmatch response.id == '#? _ > 0'. Then assert that the address city is not empty:match response.address.city == '#? _.length > 0'.
Next lesson: dot notation, def, and response extraction — how to pull values out of a response and use them in the next request.