Standalone Runner
BerryCrush can be used without JUnit through the ScenarioRunner class.
This is useful for:
Custom test frameworks
CI/CD pipeline scripts
Integration with other testing tools
Programmatic scenario execution
Overview
The ScenarioRunner provides a simple API to execute scenarios and collect results:
import org.berrycrush.berrycrush.config.Configuration
import org.berrycrush.berrycrush.dsl.berryCrush
import org.berrycrush.berrycrush.openapi.SpecRegistry
import org.berrycrush.berrycrush.runner.ScenarioRunner
// Create configuration
val config = Configuration().apply {
baseUrl = "https://api.example.com"
}
// Create spec registry and load OpenAPI spec
val specRegistry = SpecRegistry()
specRegistry.registerDefault("petstore.yaml")
// Create runner
val runner = ScenarioRunner(specRegistry, config)
// Define scenarios
val suite = berryCrush("petstore.yaml")
val scenario = suite.scenario("List pets") {
`when`("I request pets") {
call("listPets")
}
then("I get results") {
statusCode(200)
}
}
// Run and get results
val result = runner.run(listOf(scenario))
println("Passed: ${result.passed}, Failed: ${result.failed}")
Basic Usage
Running Multiple Scenarios
val scenarios = listOf(
suite.scenario("Test 1") { /* ... */ },
suite.scenario("Test 2") { /* ... */ },
suite.scenario("Test 3") { /* ... */ }
)
val result = runner.run(scenarios)
// Check overall status
when (result.status) {
ResultStatus.PASSED -> println("All tests passed!")
ResultStatus.FAILED -> println("${result.failed} tests failed")
ResultStatus.ERROR -> println("${result.errors} tests had errors")
ResultStatus.SKIPPED -> println("All tests were skipped")
}
Running a Single Scenario
val result = runner.run(scenario)
for ((scenario, scenarioResult) in result.scenarioResults) {
println("${scenario.name}: ${scenarioResult.status}")
}
Progress Callbacks
Receive callbacks as each scenario completes:
val result = runner.run(scenarios) { scenario, scenarioResult ->
when (scenarioResult.status) {
ResultStatus.PASSED -> println("✓ ${scenario.name}")
ResultStatus.FAILED -> println("✗ ${scenario.name}")
ResultStatus.ERROR -> println("! ${scenario.name}: ${scenarioResult.error?.message}")
ResultStatus.SKIPPED -> println("- ${scenario.name} (skipped)")
}
}
Manual Lifecycle Control
For fine-grained control over execution, use the lifecycle methods:
// Start the test execution lifecycle
runner.beginExecution()
try {
// Execute scenarios one by one
for (scenario in scenarios) {
val result = runner.executeScenario(scenario)
// Custom logic between scenarios
if (result.status == ResultStatus.FAILED) {
logFailure(scenario, result)
}
}
} finally {
// End the lifecycle (triggers report generation)
runner.endExecution()
}
Cross-Scenario Variable Sharing
Enable variable sharing between scenarios:
val config = Configuration().apply {
baseUrl = "https://api.example.com"
shareVariablesAcrossScenarios = true
}
val runner = ScenarioRunner(specRegistry, config)
// Scenario 1: Create resource and extract ID
val createScenario = suite.scenario("Create pet") {
`when`("I create a pet") {
call("createPet") {
body(mapOf("name" to "Fluffy"))
}
extractTo("petId", "$.id")
}
}
// Scenario 2: Use extracted ID
val getScenario = suite.scenario("Get pet") {
`when`("I get the pet") {
call("getPetById") {
pathParam("petId", $$"${petId}")
}
}
}
// Variables persist across scenarios
val result = runner.run(listOf(createScenario, getScenario))
Plugin Integration
Register plugins for lifecycle hooks:
import org.berrycrush.berrycrush.plugin.PluginRegistry
val pluginRegistry = PluginRegistry()
// Register built-in plugins
pluginRegistry.registerByName("report:text")
pluginRegistry.registerByName("report:html")
// Register custom plugins
pluginRegistry.register(MyCustomPlugin())
// Create runner with plugins
val runner = ScenarioRunner(
specRegistry = specRegistry,
configuration = config,
pluginRegistry = pluginRegistry
)
Fragment Support
Load and use fragments:
import org.berrycrush.berrycrush.model.FragmentRegistry
import org.berrycrush.berrycrush.parser.FragmentParser
// Create fragment registry
val fragmentRegistry = FragmentRegistry()
// Load fragments from files
val parser = FragmentParser()
val authFragment = parser.parse(File("fragments/auth.fragment").readText())
fragmentRegistry.register("authenticate", authFragment)
// Create runner with fragments
val runner = ScenarioRunner(
specRegistry = specRegistry,
configuration = config,
fragmentRegistry = fragmentRegistry
)
Loading Scenarios from Files
Parse and run .scenario files:
import org.berrycrush.berrycrush.parser.ScenarioParser
val parser = ScenarioParser()
// Parse a scenario file
val scenarios = parser.parse(File("scenarios/petstore.scenario").readText())
// Run all scenarios from the file
val result = runner.run(scenarios)
Complete Example
Here’s a complete example of a standalone test runner:
import org.berrycrush.berrycrush.config.Configuration
import org.berrycrush.berrycrush.model.ResultStatus
import org.berrycrush.berrycrush.openapi.SpecRegistry
import org.berrycrush.berrycrush.parser.ScenarioParser
import org.berrycrush.berrycrush.plugin.PluginRegistry
import org.berrycrush.berrycrush.runner.ScenarioRunner
import java.io.File
import kotlin.system.exitProcess
fun main(args: Array<String>) {
// Configuration
val config = Configuration().apply {
baseUrl = System.getenv("API_BASE_URL") ?: "http://localhost:8080"
timeout(30)
shareVariablesAcrossScenarios = true
}
// Load OpenAPI spec
val specRegistry = SpecRegistry()
specRegistry.registerDefault("api-spec.yaml")
// Set up plugins
val pluginRegistry = PluginRegistry()
pluginRegistry.registerByName("report:text")
// Create runner
val runner = ScenarioRunner(
specRegistry = specRegistry,
configuration = config,
pluginRegistry = pluginRegistry
)
// Parse scenario files
val parser = ScenarioParser()
val scenarioDir = File("scenarios")
val scenarios = scenarioDir.listFiles { f -> f.extension == "scenario" }
?.flatMap { parser.parse(it.readText()) }
?: emptyList()
println("Running ${scenarios.size} scenarios...")
// Execute with progress
val result = runner.run(scenarios) { scenario, scenarioResult ->
val icon = when (scenarioResult.status) {
ResultStatus.PASSED -> "✓"
ResultStatus.FAILED -> "✗"
ResultStatus.ERROR -> "!"
ResultStatus.SKIPPED -> "-"
}
println("$icon ${scenario.name}")
}
// Print summary
println()
println("=" .repeat(50))
println("Results: ${result.passed} passed, ${result.failed} failed, " +
"${result.errors} errors, ${result.skipped} skipped")
println("Duration: ${result.duration.toMillis()}ms")
println("=" .repeat(50))
// Exit with appropriate code
exitProcess(if (result.status == ResultStatus.PASSED) 0 else 1)
}
CI/CD Integration
Use the standalone runner in CI/CD pipelines:
GitHub Actions:
- name: Run API Tests
run: |
./gradlew :api-tests:run --args="--baseUrl=${{ vars.API_URL }}"
env:
API_KEY: ${{ secrets.API_KEY }}
Gradle Task:
// build.gradle.kts
tasks.register<JavaExec>("runApiTests") {
mainClass.set("com.example.ApiTestRunnerKt")
classpath = sourceSets["test"].runtimeClasspath
args = listOf(
"--baseUrl=${project.findProperty("apiUrl") ?: "http://localhost:8080"}"
)
}
Run with: ./gradlew runApiTests -PapiUrl=https://staging.example.com
API Reference
ScenarioRunner
Constructor |
Description |
|---|---|
|
Basic runner |
|
Runner with plugins |
|
Full runner with plugins and fragments |
Methods:
Method |
Description |
|---|---|
|
Execute scenarios and return aggregated result |
|
Execute with progress callback |
|
Execute single scenario |
|
Start lifecycle (calls plugin hooks) |
|
Execute one scenario (no lifecycle hooks) |
|
End lifecycle (triggers reports) |
RunResult
Property |
Description |
|---|---|
|
Overall result status |
|
Total execution duration |
|
List of (Scenario, ScenarioResult) pairs |
|
Number of passed scenarios |
|
Number of failed scenarios |
|
Number of skipped scenarios |
|
Number of error scenarios |
ScenarioResult
Property |
Description |
|---|---|
|
The executed scenario |
|
Result status |
|
Results for each step |
|
Execution start time |
|
Execution duration |
See Also
Kotlin DSL - Kotlin DSL for programmatic scenarios
Plugins - Plugin system for lifecycle hooks
Fragments - Reusable step sequences