Scenario File Syntax
This document defines the complete syntax for BerryCrush .scenario and .fragment files.
File Types
Extension |
Description |
|---|---|
|
Test scenario files |
|
Reusable step fragments |
File Encoding
All files must be UTF-8 encoded.
Complete Grammar (EBNF)
(* Top-level structure *)
scenario_file = [ parameters_block ] , { feature | scenario | fragment } ;
(* Parameters block - file-level or feature-level configuration *)
parameters_block = "parameters:" , NEWLINE , { parameter_entry | nested_param_block } ;
parameter_entry = INDENT , parameter_name , ":" , parameter_value , NEWLINE ;
nested_param_block = INDENT , identifier , ":" , NEWLINE , { INDENT , INDENT , parameter_entry } ;
(* Tags *)
tags = { tag } ;
tag = "@" , identifier ;
(* Feature block with optional parameters and background *)
feature = tags , "feature:" , feature_name , NEWLINE ,
[ INDENT , parameters_block ] ,
[ background ] , { feature_scenario } ;
background = INDENT , "background:" , NEWLINE , { step } ;
feature_scenario = INDENT , tags , ( "scenario:" | "outline:" ) ,
scenario_name , NEWLINE , { step } , [ examples ] ;
(* Scenario definition *)
scenario = tags , "scenario:" , scenario_name , NEWLINE , { step } , [ examples ] ;
(* Scenario outline with examples *)
outline = tags , "outline:" , scenario_name , NEWLINE , { step } , examples ;
(* Fragment definition *)
fragment = "fragment:" , fragment_name , NEWLINE , { step } ;
(* Step definition *)
step = INDENT , step_keyword , step_description , NEWLINE , { step_directive } ;
step_keyword = "given " | "when " | "then " | "and " | "but " ;
(* Step directives *)
step_directive = INDENT , INDENT , directive_type , [ directive_value ] , NEWLINE ;
directive_type = "call" | "assert" | "extract" | "include" | "body:" | conditional ;
(* Include directive with optional parameters *)
include_directive = "include" , fragment_name , [ NEWLINE , include_params ] ;
include_params = { INDENT , INDENT , INDENT , param_name , ":" , param_value , NEWLINE } ;
param_value = quoted_string | number | boolean | variable | json_object | json_array ;
(* Assertions - supports both built-in conditions and custom assertion patterns *)
assertion = "assert" , [ "not" ] , ( condition | custom_assertion ) ;
(* Custom assertions - any text pattern that doesn't match built-in conditions *)
(* Used with custom assertion providers registered via AssertionRegistry *)
custom_assertion = assertion_pattern ;
assertion_pattern = { text_token | quoted_string | number | variable | json_path } ;
(* Conditional branching - if/else if/else *)
conditional = if_branch , { else_if_branch } , [ else_branch ] ;
if_branch = "if" , condition , NEWLINE , { conditional_action } ;
else_if_branch = "else if" , condition , NEWLINE , { conditional_action } ;
else_branch = "else" , NEWLINE , { conditional_action } ;
conditional_action = assertion | extraction | fail_action | conditional ;
fail_action = "fail" , quoted_string ;
(* Built-in conditions - for both 'if' and 'assert' *)
condition = status_condition | jsonpath_condition | header_condition
| body_contains | schema_condition | response_time
| variable_condition ;
status_condition = "status" , ( number | status_range ) ;
status_range = digit , "xx" | number , "-" , number ;
jsonpath_condition = jsonpath , [ "not" ] , operator , [ value ] ;
header_condition = "header_" , header_name , [ "not" ] , operator , [ value ] ;
body_contains = "contains" , quoted_string ;
schema_condition = "schema" ;
response_time = "responseTime" , number ;
variable_condition = variable_path , [ "not" ] , operator , [ value ] ;
(* Operators for conditions *)
operator = "equals" | "=" | "contains" | "matches" | "exists"
| "greaterThan" | ">" | "lessThan" | "<"
| "hasSize" | "size" | "arraySize" | "notEmpty" | "in" ;
(* Examples for parameterized scenarios *)
examples = INDENT , "examples:" , NEWLINE , example_header , { example_row } ;
example_header = INDENT , INDENT , "|" , { cell , "|" } , NEWLINE ;
example_row = INDENT , INDENT , "|" , { cell , "|" } , NEWLINE ;
(* Terminals *)
feature_name = text ;
scenario_name = text ;
fragment_name = text ;
step_description = text ;
parameter_name = identifier , { "." , identifier } ;
parameter_value = quoted_string | number | boolean ;
cell = { character - "|" } ;
text = { character - NEWLINE } ;
text_token = identifier | keyword ;
identifier = letter , { letter | digit | "_" | "-" } ;
json_path = "$" , { "." , identifier | "[" , ( number | "*" ) , "]" } ;
variable = "{{" , variable_path , "}}" ;
variable_path = identifier , { "." , identifier } ;
quoted_string = '"' , { character - '"' } , '"' ;
number = [ "-" ] , digit , { digit } , [ "." , digit , { digit } ] ;
boolean = "true" | "false" ;
header_name = identifier , { "-" , identifier } ;
INDENT = " " ; (* 2 spaces *)
NEWLINE = "\n" | "\r\n" ;
Features and Background
Features provide logical grouping for related scenarios with shared setup via background steps.
Basic Feature
feature: Pet Store API
scenario: list all pets
when I list pets
call ^listPets
then I get results
assert status 200
scenario: create a pet
when I create a pet
call ^createPet
body: {"name": "Max"}
then the pet is created
assert status 201
Feature with Background
Background steps run before each scenario in the feature:
feature: Pet Operations
background:
given: setup test data
call ^createPet
body: {"name": "TestPet"}
assert status 201
extract $.id => petId
scenario: get pet by id
when: retrieve the pet
call ^getPetById
petId: {{petId}}
then: pet is returned
assert status 200
scenario: update pet
when: update the pet name
call ^updatePet
petId: {{petId}}
body: {"name": "UpdatedPet"}
then: pet is updated
assert status 200
Tagged Features
Tags on a feature are inherited by all scenarios within:
@api @regression
feature: Authentication Tests
background:
given: login
call ^login
extract $.token => authToken
@smoke
scenario: access profile
when: get profile
call ^getProfile
header_Authorization: "Bearer {{authToken}}"
then: profile is returned
assert status 200
@ignore
scenario: incomplete test
when: TODO
call ^incomplete
In this example:
“access profile” has tags:
api,regression,smoke“incomplete test” has tags:
api,regression,ignore
Quick Reference
Basic Scenario
scenario: My test scenario
given some precondition
when I perform an action
call ^operationId
then I should see a result
assert status 200
With Parameters Block
parameters:
baseUrl: "http://localhost:8080"
timeout: 60
shareVariablesAcrossScenarios: true
scenario: Test with custom configuration
when I call the API
call ^listPets
then I get results
assert status 200
Step Keywords
Keyword |
Description |
|---|---|
|
Precondition setup |
|
Action to perform |
|
Expected outcome |
|
Continuation of previous step type |
|
Exception/negative case |
Step Directives
API Call (call)
Syntax: call [using <spec-name>] ^<operationId>
when I request pets
call ^listPets
With named spec (multi-spec):
when I authenticate
call using auth ^login
Call Parameters
Parameter |
Description |
Example |
|---|---|---|
Path parameter |
Replace path variable |
|
Query parameter |
Add query string |
|
Header |
Add HTTP header |
|
Body (inline) |
Set JSON body directly |
|
Body (structured) |
Set body with properties |
|
Example with inline body:
when I create a pet
call ^createPet
petId: 123
status: "available"
header_Authorization: "Bearer {{token}}"
body: {"name": "Fluffy", "category": "dog"}
Example with structured body:
when I create a pet
call ^createPet
body:
name: Fluffy
category: dog
Assertions (assert)
Note: Assertions and
ifconditions share the same condition evaluation logic. The same operators and syntax work identically in both contexts.
Status Code
assert status 200
assert status 2xx # Range: 200-299
assert status 201-204 # Range: 201-204
Body-Level Assertions
assert contains "expected text" # Body contains substring
assert not contains "unexpected text" # Body does not contain substring
assert schema # Validate against OpenAPI schema
Response Time Assertions
assert responseTime 1000 # Response must complete within 1000ms
JSONPath Assertions
assert $.name equals "Fluffy"
assert $.id exists
assert $.pets notEmpty
assert $.count greaterThan 0
assert $.tags contains "urgent"
assert $.status in ["available", "pending"]
assert $.items hasSize 5
Header Assertions
assert header Content-Type equals "application/json"
assert header X-Request-Id exists
assert header Cache-Control contains "no-cache"
The not Keyword (Negation)
The not keyword inverts any assertion, making it a negative check. It can be placed in two positions:
At the beginning (for body-level assertions):
assert not contains "error" # Body does NOT contain "error"
After the JSONPath (for JSONPath assertions):
assert $.status not equals "sold" # Field does NOT equal "sold"
assert $.items not hasSize 0 # Array does NOT have size 0
assert $.deleted not exists # Field does NOT exist
Both positions are supported to allow natural reading: “assert body does not contain” and “assert the name does not equal”.
Operators
Operator |
Description |
Example |
|---|---|---|
|
Exact equality |
|
|
Negation (see above) |
|
|
Field exists |
|
|
Array/string not empty |
|
|
Numeric comparison |
|
|
Numeric comparison |
|
|
Body/array contains |
|
|
Value in list |
|
|
Array/string length |
|
|
Regex match |
|
|
Validate against schema |
|
Extraction (extract)
Syntax: extract <jsonPath> => <variableName>
then I capture the ID
assert status 201
extract $.id => petId
Use extracted variables with {{variableName}}:
when I get the pet
call ^getPetById
petId: {{petId}}
Conditional Assertions (if/else if/else/fail)
Conditional assertions allow branching logic based on response status, JSON path values, or headers.
Note:
ifconditions andassertstatements share the same condition evaluation logic. This means the same condition types (status, jsonpath, header) work identically in both contexts.
Basic Syntax
when I make a request
call ^operation
if status 201
# Executed if status is 201 (created)
assert $.id notEmpty
extract $.id => createdId
else if status 200
# Executed if status is 200 (already exists)
assert $.id notEmpty
else
# Executed if none of the above matched
fail "Expected status 200 or 201"
Condition Types
Both if conditions and assert statements share a unified condition syntax:
Type |
Syntax |
Example |
|---|---|---|
Status code |
|
|
Status range |
|
|
JSON path |
|
|
Header |
|
|
Body contains |
|
|
Schema validation |
|
|
Response time |
|
|
Variable |
|
|
Note: While all condition types are syntactically valid in both contexts, some are more commonly used in assertions (schema, responseTime) and some in conditionals (variable).
JSON Path Operators
Same operators work in both if and assert contexts: equals, exists, notEmpty, greaterThan, lessThan, contains, in, hasSize, matches.
if $.items greaterThan 0
assert $.items[0].id notEmpty
else
# Empty list is acceptable
assert $.items hasSize 0
Nested Conditionals
Conditionals can be nested for complex logic:
if status 200
if $.status equals "available"
assert $.price notEmpty
else
assert $.status notEmpty
else if status 201
assert $.id notEmpty
else
fail "Unexpected status"
The fail Action
Use fail to explicitly fail a scenario with a custom message:
else
fail "Expected status 200 or 201 but got something else"
Fragment Inclusion (include)
Basic fragment inclusion:
scenario: Use authentication
given I am authenticated
include authenticate
when I access protected resource
call ^getProfile
Parameterized Fragments
Fragments can accept parameters that become available as variables within the fragment’s scope:
# Fragment definition (create_user.fragment)
fragment: create_user
when creating the user
call ^createUser
body: {"name": "{{name}}", "email": "{{email}}", "age": {{age}}}
then user is created
assert status 201
# Scenario using parameterized fragment
scenario: Create specific user
given I create a user
include create_user
name: "John Doe"
email: "john@example.com"
age: 30
then the user exists
assert $.name equals "John Doe"
Parameter Types:
Type |
Example |
Notes |
|---|---|---|
String |
|
Use quotes for values with spaces |
Number |
|
Integer or decimal |
Boolean |
|
true or false |
Variable |
|
Reference existing variables |
JSON Object |
|
Inline JSON |
JSON Array |
|
Inline arrays |
Variable References in Parameters:
Parameters can reference variables defined earlier in the scenario:
scenario: Create user from context
given I have user data
set userName => "Alice"
set userEmail => "alice@example.com"
when I create the user
include create_user
name: {{userName}}
email: {{userEmail}}
age: 25
Request Body (body:)
The body: keyword supports two modes:
Inline JSON (Raw Mode)
Specify JSON directly on the same line:
when I create a pet
call ^createPet
body: {"name": "Fluffy", "status": "available"}
Structured Properties
Use indented properties to generate JSON with OpenAPI schema defaults:
when I create a pet
call ^createPet
body:
name: Fluffy
status: available
This generates a JSON body by:
Extracting default values from the OpenAPI requestBody schema
Merging with user-provided properties (user values take precedence)
Generating the final JSON
Example with partial data:
# Only specify name, other fields use schema defaults
when I create a pet
call ^createPet
body:
name: MyPet
Example with nested objects:
when I create a pet
call ^createPet
body:
name: NestedPet
metadata:
source: test
version: 1.0
Multi-line Body (Triple Quotes)
Use triple quotes (""") for multi-line raw body content. The common indentation is automatically removed:
when I create a pet
call ^createPet
body:
"""
{
"name": "Fluffy",
"status": "available",
"tags": ["cute", "friendly"]
}
"""
This sends the JSON exactly as written (with common indentation stripped). This is useful for:
Large JSON payloads that are hard to read on one line
Copying JSON from other sources
Complex nested structures
Multi-line JSON (Legacy)
Use > for multi-line raw JSON:
when I create a pet
call ^createPet
body: >
{
"name": "Fluffy",
"status": "available",
"tags": ["cute", "friendly"]
}
Auto-Generated Tests (auto:)
The auto: directive enables automatic generation of invalid request, security, and idempotency tests based on OpenAPI schema constraints and common patterns.
Syntax
call ^operationId
auto: [<test-types>]
<base-parameters>
Where <test-types> is a space-separated list of:
invalid- Generate tests that violate OpenAPI schema constraintssecurity- Generate tests with common attack payloadsmulti- Generate idempotency tests with sequential and concurrent requests
Basic Example
scenario: Auto-generated tests for createPet
when I create a pet with invalid request
call ^createPet
auto: [invalid security]
body:
name: "Fluffy"
status: "available"
if status 4xx and test.type equals invalid
# Invalid tests should return 4xx - test passed
else if status 4xx and test.type equals security
# Security tests should return 4xx - attack blocked
else
fail "Expected 4xx for {{test.type}} test: {{test.description}}"
Test Types
Invalid Tests (invalid)
Generate tests that violate OpenAPI schema constraints:
Constraint |
Test Generated |
|---|---|
|
String with length below minimum |
|
String with length above maximum |
|
Number below minimum value |
|
Number above maximum value |
|
String that violates regex pattern |
|
Invalid email format |
|
Invalid UUID format |
|
Invalid date format |
|
Missing required fields |
|
Value not in allowed enum |
Type mismatch |
Wrong type (e.g., string instead of number) |
Security Tests (security)
Generate tests with common attack payloads:
Category |
Examples |
|---|---|
SQL Injection |
|
XSS |
|
Path Traversal |
|
Command Injection |
|
LDAP Injection |
`)(uid=))( |
XXE |
|
Multi Tests (multi)
Generate idempotency tests that execute requests multiple times:
Mode |
Description |
|---|---|
Sequential |
Execute requests one after another (default: 3 requests) |
Concurrent |
Execute requests simultaneously (default: 5 requests) |
Multi tests verify:
Idempotency - Same request returns consistent results
Race conditions - Concurrent requests don’t cause data corruption
State consistency - API maintains correct state under load
Example:
scenario: Idempotency test for getPet
when I get a pet multiple times
call ^getPetById
auto: [multi]
petId: 1
then all requests succeed consistently
assert status 2xx
Configure request counts:
parameters:
multiTestSequentialCount: 5 # Run 5 sequential requests
multiTestConcurrentCount: 10 # Run 10 concurrent requests
Parameter Locations
Auto-tests are generated for parameters in different locations:
Location |
Description |
Example |
|---|---|---|
|
JSON body fields |
|
|
URL path parameters |
|
|
Query string params |
|
|
HTTP headers |
|
Context Variables
During auto-test execution, these variables are set:
Variable |
Description |
Example Values |
|---|---|---|
|
Test category |
|
|
Field being tested |
|
|
Test description |
|
|
Attack/invalid value |
|
|
Parameter location |
|
For multi-tests, additional variables are available:
Variable |
Description |
Example Values |
|---|---|---|
|
Execution mode |
|
|
Number of requests |
|
|
Whether all passed |
|
|
Total time (ms) |
|
Complete Example
scenario: Auto-generated path parameter tests for getPetById
when I get a pet with invalid ID
call ^getPetById
auto: [invalid security]
petId: 1
if status 4xx
# Test passed - invalid request rejected
else
fail "Expected 4xx for [{{test.type}}] {{test.location}} test: {{test.description}}"
scenario: Auto-generated create pet tests
when I create a pet with invalid data
call ^createPet
auto: [invalid security]
body:
name: "TestPet"
status: "available"
if status 4xx and test.type equals invalid
# Invalid input correctly rejected
else if status 4xx and test.type equals security
# Security attack blocked
else if not status 2xx
# Unexpected error
fail "Unexpected error for {{test.type}}: {{test.description}}"
Test Display Names
Auto-tests appear in test reports with descriptive names:
Invalid and Security Tests:
[invalid - minLength] request body name with value <empty string>
[invalid - type] path variable petId with value not-a-number
[security - SQL Injection] request body name with value ' OR '1'='1
[security - Path Traversal] path variable petId with value ../../etc/passwd
Multi Tests (Idempotency):
[multi:sequential] 3 requests
[multi:concurrent] 5 requests
With custom counts:
[multi:sequential] 10 requests
[multi:concurrent] 20 requests
Best Practices
Provide valid base parameters - Auto-tests modify one parameter at a time while keeping others valid
Use conditional assertions - Check
test.typeto handle invalid vs security tests differentlyExpect 4xx responses - Both invalid and security tests should be rejected by a secure API
Review generated tests - The number of tests depends on schema constraints; complex schemas generate more tests
Parameters Block
Parameters can be specified at two levels:
File-Level Parameters
Place at the top of the file to configure all scenarios:
parameters:
baseUrl: "http://localhost:8080"
timeout: 60
environment: "staging"
shareVariablesAcrossScenarios: true
logRequests: true
logResponses: true
strictSchemaValidation: false
followRedirects: true
header.Authorization: "Bearer test-token"
header.X-API-Key: "my-api-key"
autoAssertions.enabled: true
autoAssertions.schema: false
Feature-Level Parameters
Place inside a feature block to configure only scenarios in that feature:
feature: Pet CRUD Operations
parameters:
shareVariablesAcrossScenarios: true
scenario: Create pet
when: I create a pet
call ^createPet
body: {"name": "SharedPet"}
extract $.id => petId
scenario: Use shared variable
# Can access {{petId}} from previous scenario
when: I get the pet
call ^getPetById
petId: {{petId}}
then: I see the pet
assert status 200
Feature-level parameters override file-level parameters for scenarios in that feature.
Supported Parameters
Parameter |
Type |
Description |
|---|---|---|
|
String |
Override API base URL |
|
Number |
Request timeout in seconds |
|
String |
Environment name for reports |
|
Boolean |
Share extracted variables across scenarios |
|
Boolean |
Enable HTTP request logging |
|
Boolean |
Enable HTTP response logging |
|
Boolean |
Fail on schema validation warnings |
|
Boolean |
Follow HTTP redirects |
|
String |
Add/override default header |
|
Various |
Configure retry behavior |
|
Various |
Configure error context output |
|
Boolean |
Enable/disable all auto-assertions |
|
Boolean |
Auto-assert correct status code |
|
Boolean |
Auto-assert Content-Type header |
|
Boolean |
Auto-assert response matches schema |
Nested Parameter Syntax
Parameters with dot-notation prefixes (like retry.*, header.*, errorContext.*) can also
be written using nested YAML-like syntax for better readability.
Flat notation (traditional):
parameters:
retry.maxAttempts: 3
retry.delay: "1s"
retry.backoff: exponential
header.Authorization: "Bearer token"
header.X-Custom: "value"
Nested notation (more readable):
parameters:
retry:
maxAttempts: 3
delay: "1s"
backoff: exponential
header:
Authorization: "Bearer token"
X-Custom: "value"
errorContext:
includeRequestBody: true
maxBodySize: 4096
Both formats are equivalent - nested blocks are flattened to dot notation internally. You can mix flat and nested syntax in the same parameters block:
parameters:
baseUrl: "http://localhost:8080"
retry:
maxAttempts: 3
delay: "500ms"
header.Authorization: "Bearer token"
Variable Substitution
Variables are referenced using double curly braces: {{variableName}}
Sources
Bindings - From
BerryCrushBindings.getBindings()Extracted values - From
extract $.path => varNameCross-scenario (file-level) - When
shareVariablesAcrossScenarios: trueat file levelCross-scenario (feature-level) - When
shareVariablesAcrossScenarios: trueinside a featureExample rows - From
examples:table
Scope Rules
File-level sharing: Variables shared across ALL scenarios in the file
Feature-level sharing: Variables shared only within that feature’s scenarios
Standalone scenarios: Not affected by feature-level sharing
Example
parameters:
shareVariablesAcrossScenarios: true
scenario: Create and use resource
when I create a pet
call ^createPet
body: {"name": "{{petName}}", "status": "available"}
extract $.id => petId
then pet is created
assert status 201
scenario: Retrieve created resource
when I get the pet
call ^getPetById
petId: {{petId}}
then I see the pet
assert status 200
Escaping Variable Syntax
To use literal {{ or ${ in strings without variable interpolation, escape them with a backslash:
# This asserts the literal text "{{petName}}" appears in the body
assert not contains "\\{{petName}}"
# In request bodies
body: {"template": "Hello \\{{name}}"}
The escape sequences:
\\{{→ literal{{\\$→ literal$
Parameterized Scenarios (Scenario Outline)
scenario: Create different pets
when I create a pet
call ^createPet
body: {"name": "{{name}}", "category": "{{category}}"}
then pet is created
assert status 201
examples:
| name | category |
| Fluffy | cat |
| Buddy | dog |
| Tweety | bird |
This generates 3 scenario runs with different parameter combinations.
Fragment Files
Fragment files (.fragment) define reusable step sequences:
# auth.fragment
fragment: authenticate
given I have valid credentials
call using auth ^login
body: {"username": "test", "password": "test"}
then authentication succeeds
assert status 200
extract $.token => authToken
fragment: logout
when I log out
call using auth ^logout
header_Authorization: "Bearer {{authToken}}"
then session is terminated
assert status 200
Usage in scenario:
scenario: Authenticated API access
given I am authenticated
include authenticate
when I access protected endpoint
call ^getProfile
header_Authorization: "Bearer {{authToken}}"
then I see my profile
assert status 200
Custom Steps
Custom steps allow you to extend BerryCrush with reusable, domain-specific steps implemented in Kotlin.
Defining Custom Steps
Use the @Step annotation to define custom steps:
import org.berrycrush.step.Step
import org.berrycrush.step.StepContext
class PetstoreSteps {
@Step("create a test pet named {string}")
fun createTestPet(context: StepContext, name: String) {
val response = context.httpClient.post("/pet") {
json(mapOf("name" to name, "status" to "available"))
}
context.variables["petId"] = response.jsonPath.read<Int>("$.id")
}
@Step("the pet should have status {string}")
fun verifyPetStatus(context: StepContext, expectedStatus: String) {
val petId = context.variables["petId"]
val response = context.httpClient.get("/pet/$petId")
val actualStatus: String = response.jsonPath.read("$.status")
check(actualStatus == expectedStatus) {
"Expected status '$expectedStatus' but got '$actualStatus'"
}
}
}
Step Patterns
Step patterns use {string}, {int}, {float} placeholders:
Placeholder |
Matches |
Example |
|---|---|---|
|
Quoted string |
|
|
Integer |
|
|
Decimal |
|
Using Custom Steps in Scenarios
scenario: Create and verify pet
given: create a test pet named "Fluffy"
then: the pet should have status "available"
StepContext
The StepContext provides access to:
Property |
Description |
|---|---|
|
Read/write scenario variables |
|
Preconfigured HTTP client |
|
OpenAPI specs |
|
Current configuration |
Configuration
Register step classes in @BerryCrushConfiguration:
@BerryCrushSpec(paths = {"petstore.yaml"})
@BerryCrushConfiguration(stepClasses = {PetstoreSteps.class, CommonSteps.class})
public class PetstoreScenarioTest {}
Custom Assertions
Custom assertions allow you to extend BerryCrush’s assert directive with domain-specific validation logic.
Defining Custom Assertions
Use the @Assertion annotation to define custom assertions:
import org.berrycrush.assertion.Assertion
import org.berrycrush.assertion.AssertionContext
import org.berrycrush.assertion.AssertionResult
class PetstoreAssertions {
@Assertion("pet name is {string}")
fun assertPetName(context: AssertionContext, expectedName: String): AssertionResult {
val actualName: String? = context.response.jsonPath.read("$.name")
return if (actualName == expectedName) {
AssertionResult.success()
} else {
AssertionResult.failure("Expected name '$expectedName' but got '$actualName'")
}
}
@Assertion("pet is available")
fun assertPetAvailable(context: AssertionContext): AssertionResult {
val status: String? = context.response.jsonPath.read("$.status")
return if (status == "available") {
AssertionResult.success()
} else {
AssertionResult.failure("Pet status is '$status', expected 'available'")
}
}
}
Assertion Patterns
Assertion patterns use the same placeholders as custom steps:
{string}- Quoted string{int}- Integer{float}- Decimal number
Using Custom Assertions in Scenarios
scenario: Verify pet response
when: I get the pet
call ^getPetById
petId: 123
then: the pet exists
assert status 200
assert pet name is "Fluffy"
assert pet is available
AssertionContext
The AssertionContext provides access to:
Property |
Description |
|---|---|
|
HTTP response with jsonPath access |
|
Current scenario variables |
|
Current configuration |
AssertionResult
Return from assertion methods:
AssertionResult.success()- Assertion passedAssertionResult.failure("message")- Assertion failed with message
Configuration
Register assertion classes in @BerryCrushConfiguration:
@BerryCrushSpec(paths = {"petstore.yaml"})
@BerryCrushConfiguration(
stepClasses = {PetstoreSteps.class},
assertionClasses = {PetstoreAssertions.class}
)
public class PetstoreScenarioTest {}
Best Practices
1. Use Descriptive Names
# Good
scenario: Create pet with valid data returns 201
# Avoid
scenario: Test1
3. Extract Values for Data Flow
when I create a pet
call ^createPet
body: {"name": "Test"}
extract $.id => petId
when I retrieve the pet
call ^getPetById
petId: {{petId}}
4. Use Fragments for Common Setup
# fragments/auth.fragment
fragment: authenticate
given I log in
call using auth ^login
body: {"username": "test", "password": "test"}
then I have a token
extract $.token => authToken
5. Enable Variable Sharing for Workflows
parameters:
shareVariablesAcrossScenarios: true
scenario: Step 1 - Create
# Creates resource, extracts ID
scenario: Step 2 - Update
# Uses ID from Step 1
scenario: Step 3 - Delete
# Uses ID from Step 1
Error Messages
Parse Errors
ScenarioParseException: Line 5: Expected step keyword (given/when/then/and/but)
Found: "invalid text here"
Runtime Errors
ConfigurationException: Operation 'unknownOperation' not found in OpenAPI spec
AssertionError: Expected status 200 but got 404
Request: GET /api/pets/999
Comments
Lines starting with
#are comments: