BerryCrush Architecture
This document describes the high-level architecture of BerryCrush and how its components interact.
System Overview
┌─────────────────────────────────────────────────────────────────────────────┐
│ BerryCrush System │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌───────────┐ │
│ │ Kotlin │ │ Scenario │ │ JUnit 5 │ │ Spring │ │
│ │ DSL │ │ Files │ │ Tests │ │ Boot │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ └─────┬─────┘ │
│ │ │ │ │ │
│ └───────────┬───────┴───────────────────┼───────────────────┘ │
│ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ ScenarioLoader │ │ BerryCrushTest- │ │
│ │ │ │ Engine │ │
│ └────────┬────────┘ └────────┬────────┘ │
│ │ │ │
│ └────────────┬──────────────┘ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ ScenarioExecutor │◄──────┐ │
│ └────────┬────────┘ │ │
│ │ │ │
│ ┌────────────────────────┼─────────────────┼──────────┐ │
│ ▼ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ ┌──────────┐ ┌───────────┐ │
│ │ SpecRegistry│ │ HttpRequest │ │ Plugin │ │ Step │ │
│ │ │ │ Builder │ │ Registry │ │ Registry │ │
│ └─────────────┘ └──────┬──────┘ └──────────┘ └───────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ HTTP Client │ │
│ │ (java.net.http) │ │
│ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
Core Components
1. Scenario Definition Layer
Scenarios can be defined in two ways:
1.1 Text-Based Scenario Files (.scenario)
Human-readable BDD-style files parsed by the ScenarioLoader:
scenario: List all pets
when I request the pet list
call ^listPets
then I receive pets
assert status 200
1.2 Kotlin DSL
Type-safe programmatic scenario definition:
suite.scenario("List all pets") {
`when`("I request the pet list") {
call("listPets")
}
then("I receive pets") {
statusCode(200)
}
}
2. Parsing and Loading
┌────────────────┐ ┌────────────────┐ ┌────────────────┐
│ Lexer │────▶│ Parser │────▶│ ScenarioLoader │
│ │ │ │ │ │
│ Tokenizes text │ │ Builds AST │ │ Creates models │
└────────────────┘ └────────────────┘ └────────────────┘
Key Classes:
Lexer- Tokenizes scenario file contentParser- Builds Abstract Syntax Tree from tokensScenarioLoader- Transforms AST into executableScenarioobjects
3. OpenAPI Integration
BerryCrush uses an abstraction layer to support multiple OpenAPI versions (3.0.x, 3.1.x, and future versions).
┌─────────────────┐ ┌───────────────────┐ ┌─────────────────┐
│ OpenAPI Spec │────▶│ OpenApiParser │────▶│ OpenApiSpec │
│ (YAML/JSON) │ │ │ │ (interface) │
│ 3.0.x / 3.1.x │ │ Version-agnostic │ │ │
└─────────────────┘ └───────────────────┘ └────────┬────────┘
│
┌──────────────────────────────────┘
▼
┌─────────────────┐ ┌───────────────────┐ ┌─────────────────┐
│ SpecRegistry │────▶│ OperationResolver │────▶│ HttpRequest │
│ │ │ │ │ Builder │
│ Manages specs │ │ Resolves ops by │ │ │
│ (multi-API) │ │ operationId │ │ │
└─────────────────┘ └───────────────────┘ └─────────────────┘
Key Interfaces:
OpenApiSpec- Unified interface for accessing OpenAPI specs (any version)OpenApiParser- Parses YAML/JSON intoOpenApiSpecabstractionOpenApiVersion- Enum for version detection (V3_0_X, V3_1_X, etc.)
Key Classes:
SpecRegistry- Manages one or more OpenAPI specificationsOperationResolver- ResolvesoperationIdto HTTP method, path, and parametersSwaggerParserAdapter- DefaultOpenApiParserimplementation
Version-Specific Features:
3.1.x webhooks accessible via
spec.webhooksJSON Schema 2020-12 support via
SchemaSpecabstractionFeature detection:
spec.hasWebhooks(),spec.hasComponents()
For detailed documentation, see OpenAPI Version Support.
4. Execution Engine
┌─────────────────┐
│ ScenarioExecutor│
└────────┬────────┘
│
├── For each step:
│
├──────────────┐
│ ▼
│ ┌──────────────────┐
│ │ StepRegistry │
│ │ │
│ │ Custom step │
│ │ lookup │
│ └────────┬─────────┘
│ │
│ │ Found?
│ │
│ ┌────────┴─────────┐
│ │ │
▼ ▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ HttpRequestBuilder│ │ Custom Step │
│ │ │ Execution │
│ Build request │ │ │
│ from operation │ │ User-defined │
└────────┬─────────┘ └──────────────────┘
│
▼
┌──────────────────┐
│ ResponseHandler │
│ │
│ Process response │
│ Run assertions │
│ Extract values │
└──────────────────┘
Key Classes:
ScenarioExecutor- Orchestrates scenario executionHttpRequestBuilder- Constructs HTTP requests from OpenAPI operationsResponseHandler- Processes responses, runs assertions, extracts valuesStepRegistry- Manages custom step definitions
5. Plugin System
┌─────────────────────────────────────────────────────────────────┐
│ Plugin System │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ │
│ │ PluginRegistry │ │
│ └────────┬────────┘ │
│ │ │
│ │ Dispatches lifecycle events │
│ │ │
│ ├─────────────────────────────────────────────────┐ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ ReportPlugin │ │ LoggingPlugin │ │ Custom Plugin │ │
│ │ │ │ │ │ │ │
│ │ - TextReport │ │ - Console │ │ - User-defined │ │
│ │ - JsonReport │ │ - HTTP Logger │ │ │ │
│ │ - JunitReport │ │ │ │ │ │
│ │ - XmlReport │ │ │ │ │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
Plugin Lifecycle:
onTestExecutionStart()- Before first scenarioonScenarioStart(context)- Before each scenarioonStepStart(context)- Before each steponStepEnd(context, result)- After each steponScenarioEnd(context, result)- After each scenarioonTestExecutionEnd()- After all scenarios
6. JUnit Integration
┌─────────────────────────────────────────────────────────────────┐
│ JUnit Platform │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────┐ │
│ │ BerryCrushTestEngine│ ◄── Registered via ServiceLoader │
│ │ (ENGINE_ID = │ META-INF/services/ │
│ │ "berrycrush") │ org.junit.platform.engine.TestEngine│
│ └──────────┬──────────┘ │
│ │ │
│ │ discover() │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ ScenarioDiscovery │ │
│ │ │ │
│ │ Finds @BerryCrush- │ │
│ │ Scenarios classes │ │
│ └──────────┬──────────┘ │
│ │ │
│ │ Creates test descriptors │
│ ▼ │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ ClassTestDescriptor │──│ ScenarioFileDesc. │ │
│ │ │ │ │ │
│ │ Per test class │ │ Per .scenario file │ │
│ └─────────────────────┘ └──────────┬──────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ScenarioTestDescriptor│ │
│ │ │ │
│ │ Per scenario │ │
│ └─────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
7. Spring Context Integration
┌─────────────────────────────────────────────────────────────────┐
│ Spring Integration │
├─────────────────────────────────────────────────────────────────┤
│ │
│ @SpringBootTest │
│ @BerryCrushContextConfiguration(bindings = MyBindings.class) │
│ │
│ ┌────────────────────┐ ┌────────────────────┐ │
│ │ SpringBindings- │────▶│ SpringContext- │ │
│ │ Provider │ │ Adapter │ │
│ │ │ │ │ │
│ │ ServiceLoader SPI │ │ Manages Spring │ │
│ │ discovery │ │ ApplicationContext │ │
│ └────────────────────┘ └─────────┬──────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────┐ │
│ │ ApplicationContext │ │
│ │ │ │
│ │ @LocalServerPort │ │
│ │ @Autowired beans │ │
│ └────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
Data Flow
Scenario Execution Flow
1. Load OpenAPI spec(s)
│
▼
2. Parse scenario files / Build from DSL
│
▼
3. For each scenario:
│
├── 3a. Execute background steps (if any)
│
├── 3b. For each step:
│ │
│ ├── Look up custom step definition
│ │ │
│ │ └── If found: Execute custom step
│ │
│ ├── If API call step:
│ │ │
│ │ ├── Resolve operation from OpenAPI spec
│ │ │
│ │ ├── Build HTTP request
│ │ │ - Substitute variables ({{var}})
│ │ │ - Apply path/query/header params
│ │ │ - Set request body
│ │ │
│ │ ├── Send request
│ │ │
│ │ └── Process response
│ │ - Schema validation (if enabled)
│ │ - Run assertions
│ │ - Extract values
│ │
│ └── Record step result
│
└── 3c. Record scenario result
│
▼
4. Generate reports
Variable Substitution
Variables are stored in ExecutionContext and can be:
Predefined bindings - From
BerryCrushBindings.getBindings()Extracted values - From response via JSONPath (
extract $.id => petId)Cross-scenario - When
shareVariablesAcrossScenarios = true
Substitution syntax: {{variableName}}
Key Design Decisions
1. OpenAPI-First Validation
All API calls are validated against the OpenAPI specification, ensuring:
Request parameters match schema
Response status codes are expected
Response bodies conform to schemas
2. Pluggable Step Definitions
Four mechanisms for custom steps:
Annotation-based -
@Step("pattern with {int}")Registration API -
registry.register(stepDefinition)Kotlin DSL -
step("pattern") { ... }Package scanning - Auto-discovery
3. Modular Design
The library is split into modules:
core- Standalone execution, no framework dependenciesjunit- JUnit 5 integrationspring- Spring Boot integration
4. Priority-Based Plugin Execution
Plugins execute in priority order (lower first), enabling:
Setup plugins (negative priority)
Standard plugins (0)
Cleanup plugins (positive priority)
Thread Safety
SpecRegistryis thread-safe for read accessExecutionContextis not thread-safe (one per scenario)PluginRegistrydispatches events sequentiallyHTTP client uses
HttpClient(thread-safe)
Error Handling
Scenario Parsing Errors
ScenarioParseExceptionwith line numbers and detailed messages
Execution Errors
ConfigurationExceptionfor setup issuesAssertionErrorfor failed assertionsStepExecutionExceptionfor step execution failures
Plugin Errors
Plugin exceptions fail the entire test run (fail-fast)
Built-in plugins handle errors gracefully