Auto-Test Provider Extensibility
BerryCrush’s auto-test feature supports custom test providers through Java’s ServiceLoader pattern. This allows users to add custom invalid request tests, security test payloads, and multi-request idempotency tests without modifying the core library.
Overview
The auto-test system uses three types of providers:
InvalidTestProvider - Generates invalid values for schema constraint testing
SecurityTestProvider - Generates security attack payloads
MultiTestProvider - Executes multi-request idempotency tests
All types are discovered automatically via ServiceLoader, allowing you to add custom providers by simply adding classes to your project.
Built-in Providers
Invalid Test Providers
Test Type |
Description |
|---|---|
minLength |
Strings shorter than minLength constraint |
maxLength |
Strings longer than maxLength constraint |
pattern |
Strings not matching the pattern |
format |
Invalid format values (email, uuid, date, etc.) |
enum |
Values not in the enum list |
minimum |
Numbers below the minimum |
maximum |
Numbers above the maximum |
type |
Wrong type values (e.g., string for number field) |
required |
Missing required fields |
minItems |
Arrays with fewer items than minItems |
maxItems |
Arrays with more items than maxItems |
Security Test Providers
Test Type |
Display Name |
Description |
|---|---|---|
SQLInjection |
SQL Injection |
SQL injection attack payloads |
XSS |
XSS |
Cross-site scripting payloads |
PathTraversal |
Path Traversal |
Path traversal attack patterns |
CommandInjection |
Command Injection |
Shell command injection payloads |
LDAPInjection |
LDAP Injection |
LDAP query injection payloads |
XXE |
XXE |
XML External Entity payloads |
HeaderInjection |
Header Injection |
HTTP header injection payloads |
Multi Test Providers
Test Type |
Display Name |
Description |
|---|---|---|
sequential |
Sequential Idempotency |
Executes requests one after another |
concurrent |
Concurrent Idempotency |
Executes requests in parallel using a thread pool |
Creating Custom Providers
Custom Invalid Test Provider
Create a class implementing InvalidTestProvider:
package com.example
import org.berrycrush.autotest.provider.InvalidTestProvider
import org.berrycrush.autotest.provider.InvalidTestValue
import io.swagger.v3.oas.models.media.Schema
class NumericOverflowProvider : InvalidTestProvider {
// Unique identifier for test reports and excludes
override val testType: String = "numericOverflow"
// Higher priority overrides built-in providers with same testType
override val priority: Int = 100
override fun canHandle(schema: Schema<*>): Boolean =
schema.type == "integer" || schema.type == "number"
override fun generateInvalidValues(
fieldName: String,
schema: Schema<*>,
): List<InvalidTestValue> = listOf(
InvalidTestValue(
value = Long.MAX_VALUE,
description = "Numeric overflow value",
),
InvalidTestValue(
value = Double.POSITIVE_INFINITY,
description = "Infinity value",
),
)
}
Custom Security Test Provider
Create a class implementing SecurityTestProvider:
package com.example
import org.berrycrush.autotest.ParameterLocation
import org.berrycrush.autotest.provider.SecurityTestProvider
import org.berrycrush.autotest.provider.SecurityPayload
class NoSqlInjectionProvider : SecurityTestProvider {
// Unique identifier for excludes
override val testType: String = "NoSQLInjection"
// Human-readable name for test reports
override val displayName: String = "NoSQL Injection"
// Higher priority overrides built-in providers with same testType
override val priority: Int = 100
override fun applicableLocations(): Set<ParameterLocation> =
setOf(ParameterLocation.BODY, ParameterLocation.QUERY)
override fun generatePayloads(): List<SecurityPayload> = listOf(
SecurityPayload(
name = "MongoDB $ne injection",
payload = "{\"\$ne\": null}",
),
SecurityPayload(
name = "MongoDB $where injection",
payload = "{\"\$where\": \"sleep(5000)\"}",
),
)
}
Registering via ServiceLoader
Create a service configuration file in your project:
src/main/resources/META-INF/services/org.berrycrush.autotest.provider.InvalidTestProvider:
com.example.NumericOverflowProvider
src/main/resources/META-INF/services/org.berrycrush.autotest.provider.SecurityTestProvider:
com.example.NoSqlInjectionProvider
The providers will be automatically discovered and registered when the auto-test system initializes.
Provider Priority
Each provider has a priority property (default: 0 for built-in, 100 for user providers):
Higher priority providers override lower priority ones with the same
testTypeEqual priority: later registration wins
Built-in providers have priority 0
User providers should use priority >= 100 to override built-in
Excluding Test Types
Use the excludes option in your scenario to skip certain test types:
auto-test:
operations: [createPet]
types: [invalid, security]
excludes: [SQLInjection, maxLength, myCustomType]
The excludes option works with both built-in and custom provider test types.
Programmatic Registration
You can also register providers programmatically:
val registry = AutoTestProviderRegistry.withDefaults()
registry.registerInvalid(MyCustomInvalidProvider())
registry.registerSecurity(MyCustomSecurityProvider())
val generator = AutoTestGenerator(openApi, registry)
Or create an empty registry for full control:
val registry = AutoTestProviderRegistry.empty()
// Only your custom providers will be used
registry.registerInvalid(MyOnlyProvider())
Test Type Naming Conventions
Use camelCase for
testType(e.g.,numericOverflow,NoSQLInjection)Use human-readable names for
displayName(e.g., “Numeric Overflow”, “NoSQL Injection”)The
testTypeis used for:Test identification and deduplication
excludesconfigurationProvider override matching
The
displayNameis used for:Test reports (IntelliJ, JUnit XML)
Scenario output logs
Multi-Test Providers
Multi-test providers support idempotency testing by executing requests multiple times in different modes. They are used with the auto: [multi] directive to verify API operations produce consistent results.
MultiTestProvider Interface
interface MultiTestProvider {
/**
* Unique identifier for this multi-test mode.
* Used for display names in test reports: [multi:{testType}]
* Used in excludes configuration: excludes: [{testType}]
*/
val testType: String
/**
* Human-readable display name for test reports.
* Defaults to testType if not overridden.
*/
val displayName: String get() = testType
/**
* Priority for provider override. Higher values = higher priority.
* User-provided providers default to 100, built-in default to 0.
*/
val priority: Int get() = 0
/**
* Execute multi-request test.
* @param count Number of requests to execute
* @param executor Function that executes a single request
* @return Results of all requests and aggregate information
*/
fun executeMultiTest(
count: Int,
executor: (requestIndex: Int) -> RequestResult,
): MultiTestResult
}
Built-in Multi-Test Providers
Test Type |
Display Name |
Description |
|---|---|---|
|
Sequential Idempotency |
Executes requests one after another, verifying sequential idempotency |
|
Concurrent Idempotency |
Executes requests in parallel using a thread pool (max 20 threads), verifying concurrent access safety |
Custom Multi-Test Provider
Create a class implementing MultiTestProvider:
package com.example
import org.berrycrush.autotest.MultiMode
import org.berrycrush.autotest.MultiTestResult
import org.berrycrush.autotest.RequestResult
import org.berrycrush.autotest.provider.MultiTestProvider
class RetryMultiTestProvider : MultiTestProvider {
// Unique identifier for this provider
override val testType: String = "RETRY"
// Human-readable name for test reports
override val displayName: String = "Retry Test"
// Higher priority overrides built-in providers
override val priority: Int = 100
override fun executeMultiTest(
count: Int,
executor: () -> RequestResult,
): MultiTestResult {
val results = mutableListOf<RequestResult>()
var lastResult: RequestResult? = null
// Execute with exponential backoff
repeat(count) { attempt ->
if (attempt > 0) {
Thread.sleep((100 * attempt).toLong())
}
lastResult = executor()
results.add(lastResult!!)
// Stop if successful
if (lastResult!!.statusCode in 200..299) {
return@repeat
}
}
val totalDuration = results.sumOf { it.durationMs }
val passed = lastResult?.statusCode in 200..299
return MultiTestResult(
mode = MultiMode.SEQUENTIAL, // or create custom mode
requestCount = results.size,
results = results,
totalDurationMs = totalDuration,
passed = passed,
failureReason = if (!passed) "All retry attempts failed" else null,
)
}
}
Registering Multi-Test Providers
Create a service configuration file:
src/main/resources/META-INF/services/org.berrycrush.autotest.provider.MultiTestProvider:
com.example.RetryMultiTestProvider
Programmatic Registration
val registry = AutoTestProviderRegistry.withDefaults()
registry.registerMulti(RetryMultiTestProvider())
Configuration Parameters
Multi-test execution can be configured via parameters:
Parameter |
Default |
Description |
|---|---|---|
|
3 |
Number of sequential requests |
|
5 |
Number of concurrent requests |
Set in scenario files (file-level):
parameters:
multiTestSequentialCount: 5
multiTestConcurrentCount: 10
At the feature level:
feature: Idempotency Tests
parameters:
multiTestSequentialCount: 10
multiTestConcurrentCount: 20
scenario: Multi test
when: I test the API
call ^operation
auto: [multi]
Or at the step level (in the call directive):
when: I stress test with custom counts
call ^operation
auto: [multi]
multiTestSequentialCount: 5
multiTestConcurrentCount: 10
Step-level parameters take precedence over file-level and feature-level parameters.
Kotlin DSL Auto-Test API
Auto-tests can also be configured using the Kotlin DSL. The DSL provides a type-safe way to enable auto-test generation.
Basic Usage
scenario("Create pet with auto-tests") {
whenever("I create a pet") {
call("createPet") {
body("""{"name": "Fluffy", "category": "cat"}""")
// Enable specific auto-test types
autoTest(AutoTestType.INVALID, AutoTestType.SECURITY)
}
}
}
Using Boolean Parameters
call("createPet") {
// Alternative API using booleans
autoTest(invalid = true, security = true, multi = false)
}
Multi-Request Idempotency Tests
scenario("Idempotency test") {
whenever("I send multiple requests") {
call("listPets") {
// Enable multi-request testing
autoTest(AutoTestType.MULTI)
}
}
}
Custom Multi-Test Parameters
Configure request counts at the suite/configuration level:
val suite = BerryCrushSuite.create()
suite.configuration.multiTestSequentialCount = 5
suite.configuration.multiTestConcurrentCount = 10
suite.scenario("Custom counts") {
whenever("I stress test the API") {
call("listPets") {
autoTest(AutoTestType.MULTI)
}
}
}
Excluding Test Categories
Exclude specific test categories from auto-generation:
call("createPet") {
autoTest(AutoTestType.INVALID, AutoTestType.SECURITY)
// Exclude specific categories
excludes("XSS", "minLength")
}
Test Display Names
DSL auto-tests use the same display name format as scenario files:
Test Type |
Display Name Format |
|---|---|
Invalid |
|
Security |
|
Multi (Sequential) |
|
Multi (Concurrent) |
|