Skip to main content

ASSERTIONS

Schema Validation

Validate complex API payloads using Karate's schema validation - simpler and more powerful than JSON-schema, with zero dependencies and inline assertions in a single step.

Why Use Schema Validation

  • Simpler than JSON-schema: No external dependencies or complex DSL syntax
  • Single-step assertions: Mix type, domain, and business logic validations together
  • Flexible patterns: Reusable schemas, conditional validation, cross-field checks
  • Better error messages: Clear feedback showing expected vs actual values

Simple Schema Validation

Feature: Simple schema validation

Scenario: Basic type validation
Given url baseUrl
And path 'users', 123
When method get
Then status 200
And match response == {
id: '#number',
name: '#string',
email: '#regex .+@.+',
active: '#boolean'
}

Optional Fields

Feature: Optional field validation

Scenario: Schema with optional fields
Given url baseUrl
And path 'api/profiles'
When method get
Then status 200
And match response == {
id: '#number', # Required
name: '#string', # Required
avatar: '##string', # Optional - can be missing
bio: '##string', # Optional - can be missing
phone: '##null', # Optional - can be null or missing
verified: '#boolean' # Required
}

Array Validation

Feature: Array validation

Scenario: Array size and type patterns
Given url baseUrl
And path 'api/products'
When method get
Then status 200
And match response == {
products: '#[]', # Must be array (any size)
featured: '#[5]', # Must be array of exactly 5 items
categories: '#[] #string', # Array of strings (any size)
topRated: '#[3] #object', # Array of exactly 3 objects
tags: '#[]? _.length > 0' # Custom array validation
}
Array Validation Patterns
  • Use #[] for any array size
  • Use #[N] for specific size (e.g., #[5])
  • Use #[] #string to validate element types
  • Use #[]? expression for custom validation logic

Array Elements with Schemas

Feature: Array element validation

Scenario: Validate each array element
Given url baseUrl
And path 'api/orders'
When method get
Then status 200
And match each response.orders == {
id: '#number',
customer: '#object',
items: '#[]',
total: '#number? _ > 0',
status: '#regex (pending|confirmed|shipped)'
}

Reusable Schemas

Feature: Reusable schemas

Background:
# Define reusable schemas
* def userSchema = {
id: '#number',
name: '#string',
email: '#regex .+@.+',
active: '#boolean'
}
* def addressSchema = {
street: '#string',
city: '#string',
zipCode: '#regex \d{5}'
}

Scenario: Apply reusable schema
Given url baseUrl
And path 'users', 123
When method get
Then status 200
And match response == userSchema

Scenario: Combine multiple schemas
Given url baseUrl
And path 'users', 123
When method get
Then status 200
And match response == karate.merge(userSchema, {
address: addressSchema,
preferences: { theme: '#regex (light|dark)' }
})
Schema Reuse

Define schemas once in the Background section and reuse them across multiple scenarios. Use karate.merge() to combine schemas for complex validations.

Advanced Schema Patterns

Dynamic Schemas

Feature: Dynamic schema validation

Scenario: Conditional schema based on response
Given url baseUrl
And path 'api/dynamic-data'
When method get
Then status 200
* def responseType = response.type
* def schema = responseType == 'user' ?
{ id: '#number', name: '#string', email: '#string' } :
responseType == 'product' ?
{ id: '#number', name: '#string', price: '#number' } :
{ id: '#number', type: '#string' }
* match response == schema

Scenario: Environment-specific schema
* def env = karate.env || 'dev'
Given url baseUrl
And path 'api/config'
When method get
Then status 200
* def baseSchema = { version: '#string', features: '#[] #string' }
* def envSchema = env == 'prod' ?
karate.merge(baseSchema, { debugInfo: '#notpresent' }) :
karate.merge(baseSchema, { debugInfo: '#object' })
* match response == envSchema

Nested Complex Schemas

Feature: Nested schema validation

Scenario: Multi-level nested structures
Given url baseUrl
And path 'api/user-profile'
When method get
Then status 200
And match response == {
user: {
id: '#number',
profile: {
personal: {
firstName: '#string',
lastName: '#string',
age: '#number? _ >= 18'
},
contact: {
email: '#regex .+@.+',
phone: '##regex \+?[0-9\-\s]+'
},
preferences: {
theme: '#regex (light|dark)',
notifications: {
email: '#boolean',
sms: '#boolean'
}
}
},
permissions: '#[] #string'
}
}

Cross-Field Validation

Feature: Cross-field validation

Scenario: Validate calculated fields
Given url baseUrl
And path 'api/financial-summary'
When method get
Then status 200
And match response == {
transactions: '#[]',
summary: {
count: '#? _ == $.transactions.length',
total: '#number',
average: '#? _ == $.total / $.count',
breakdown: {
income: '#number? _ >= 0',
expenses: '#number? _ >= 0',
net: '#? _ == $.income - $.expenses'
}
}
}

External Schema Files

Feature: External schemas

Scenario: Load schema from file
Given url baseUrl
And path 'api/users'
When method get
Then status 200
* def userSchema = read('schemas/user-schema.json')
And match each response.users == userSchema

Scenario: Combine external schemas
* def profileSchema = read('schemas/profile-schema.json')
* def addressSchema = read('schemas/address-schema.json')
* def completeSchema = karate.merge(profileSchema, {
address: addressSchema
})
Given url baseUrl
And path 'api/users', 123
When method get
Then status 200
And match response == completeSchema

Schema Inheritance

Feature: Schema inheritance

Scenario: Extend base schema
# Base schema from file
* def baseSchema = read('schemas/base-entity-schema.json')
* def productSchema = karate.merge(baseSchema, {
name: '#string',
price: '#number? _ > 0',
category: '#string',
inventory: {
inStock: '#boolean',
quantity: '#number? _ >= 0'
}
})
Given url baseUrl
And path 'api/products', 123
When method get
Then status 200
And match response == productSchema

Next Steps

Master schema validation and continue with: