Built-in Assertions — match, contains, Fuzzy Matching

9 min read

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'
}

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. Use match contains for 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. #? _ > 0 means "this number is greater than zero." The underscore is the placeholder — it's not a Python _ or a JavaScript discard. Writing #? value > 0 instead of #? _ > 0 silently fails because value is undefined.
  • Using JavaScript assert or TestNG assertions instead of match. 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." Prefer match everywhere.

🎯 Practice task

Write assertions that use every major match variant. 30–40 minutes.

  1. GET /users/1 from JSONPlaceholder. Write match response == with fuzzy markers for every field — use #number for id, #string for name, ##string for phone and website (they may be present or absent). Run green.
  2. Write match response contains { name: '#string', email: '#regex .+@.+' }. Note it passes even though the response has more fields. Change contains to == and observe the failure — the error shows what extra fields caused the mismatch. Restore.
  3. GET /users (the full list). Write match 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.
  4. Add match response == '#[10]' to the same scenario — assert there are exactly 10 users. Run green.
  5. Write a scenario that POSTs a new user and then uses match response.id == '#notnull' and match response.id == '#number' to assert the returned id. Confirm both pass.
  6. Regex marker: GET /users/1 and add match 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.
  7. Stretch: write a scenario using #? for a custom validation. Assert that response.id > 0 using match 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.

// tip to track lessons you complete and pick up where you left off across devices.