Architecture¶
System Overview¶
OpenAPI Test Generator is a Kotlin/JVM toolchain that turns OpenAPI specs into executable tests or test-suite artifacts. It targets:
- CLI users who want a fast generator for local runs or CI
- Gradle users who want generation wired into build pipelines
- Integrators who embed the core engine to customize rules, data providers, or generators
The design centers on a small, deterministic core that owns parsing, rule application, and test-suite assembly, with optional feature modules (template generation, pattern-based values) injected explicitly by CLI/Gradle to avoid reflection-based discovery.
What This Tool Tests¶
OpenAPI Test Generator validates infrastructure-level behavior - the contract compliance layer between API consumers and your controllers. This is distinct from business logic testing.
Infrastructure validation (what we test)¶
- Parameter validation: Type checking, format constraints (date, email, uuid), min/max bounds, enum membership, required vs optional
- Request body validation: JSON schema compliance, nested object structure, array limits, property constraints
- Security enforcement: Authentication presence, valid credentials format, correct security scheme usage
Business logic (what we don't test)¶
- Data semantics (e.g., "order total must equal sum of line items")
- Authorization rules beyond API-level security (e.g., "users can only access their own orders")
- Side effects (e.g., "creating an order should send a confirmation email")
- State transitions (e.g., "orders can only be cancelled before shipping")
The generated tests verify that your API rejects invalid requests with appropriate error codes (400, 401, 403) - they don't verify what happens when valid requests are processed.
Related docs¶
- Documentation index: docs home
- Getting started: Getting started
- How-to guides: Configuration, Generators, Negative testing
- Development: development setup
- Core module: core
- Reference:
Module Dependency Graph¶
Layered Architecture¶
flowchart TB
subgraph Entry["Entry Points"]
CLI["CLI<br/>(native + JVM)"]
Plugin["Gradle Plugin<br/>(OpenApiTestGeneratorTask)"]
end
subgraph Dist["Distribution Bundle"]
Runner["TestGenerationRunner<br/>(orchestration, reporter, default wiring)"]
end
subgraph Features["Feature Modules"]
PatternSupport["pattern-support<br/>(regex rule + module wiring)"]
Template["generator-template<br/>(Mustache codegen)"]
end
subgraph Core["Core"]
Providers["Providers<br/>(param, body, auth)"]
Rules["Rules<br/>(schema, auth)"]
Engine["TestGenerationEngine<br/>(orchestration, config)"]
end
subgraph Values["Value Generation"]
PatternValue["pattern-value<br/>(regex value generator)"]
ExampleValue["example-value<br/>(value providers, schema merger)"]
end
subgraph Model["Model"]
Types["TestCase, TestSuite, Outcome,<br/>GenerationReport, ErrorMode"]
end
CLI --> Runner
Plugin --> Runner
Runner --> Template
Runner --> PatternSupport
PatternSupport --> Core
Template --> Core
PatternSupport --> PatternValue
Core --> ExampleValue
PatternValue --> ExampleValue
ExampleValue --> Model Value Provider Dependency Chain¶
The value generation stack follows a strict dependency direction:
flowchart LR
PV["pattern-value"] --> EV["example-value"] --> M["model"] pattern-value contains:
PatternValueProvider(implements SchemaValueProvider SPI)PatternValueGenerator(regexp-gen wrapper)- No core dependency
example-value contains:
SchemaValueProviderSPI interface- Built-in providers (enum, date, uuid, etc.)
SchemaMerger,CartesianProduct
This layering enables standalone use of pattern-value for regex-based string generation without pulling in the test generation framework.
Module Responsibilities¶
| Module | Responsibility | Key Types |
|---|---|---|
| build-logic | Convention plugins for centralized build config | testgen.kotlin-base, testgen.quality, testgen.library |
| model | Data classes, error types | TestCase, TestSuite, Outcome, GenerationError |
| example-value | Schema value generation SPI + builtins | SchemaValueProvider, SchemaExampleValueGenerator, SchemaMerger |
| core | Parsing, generation, orchestration | TestGenerationEngine, providers, rules, TestSuiteWriter |
| pattern-value | Regex-based value generation | PatternGenerationOptions, PatternValueGenerator, PatternValueProvider |
| pattern-support | Pattern integration module | PatternSupportModule, PatternModuleSettingsExtractor |
| generator-template | Mustache-based code generation | TemplateGeneratorModule, TemplateArtifactGenerator |
| distribution-bundle | Bundles modules, execution wiring | TestGenerationRunner, TestGenerationReporter, DistributionDefaults |
| plugin | Gradle build integration | OpenApiTestGeneratorPlugin, OpenApiTestGeneratorTask |
| cli | Command-line interface + native | GenerateCommand, Picocli wiring |
Data Flow¶
- Entry point: CLI or Gradle creates a
TestGenerationRunner(viawithDefaults()or builder) with an environment-specificTestGenerationReporter. - Configuration resolution:
TestGenerationRunner.execute()callsTestGeneratorExecutionOptionsFactory.fromConfigto merge overrides over YAML config and apply defaults. - Parse OpenAPI:
OpenApiSpecParser.parseOpenApiloads and resolves the OpenAPI document withParseOptions().apply { isResolveFully = true }. - Filter operations: If
includeOperationsis set, the engine filters paths/methods before generation.ignoreTestCasesis applied later, after suites are assembled. - Build generation pipeline:
TestGenerationEngine.createProcessorwires schema merging, value providers, rules, and providers viaTestGeneratorConfigurer. - Generate suites per operation (from
paths):ValidCaseBuilderconstructs a baseline valid test case from operation parameters, request body, and security requirements.TestGenerationContextwraps the valid case, OpenAPI model, and helper services (data providers, schema merger).ProviderOrchestratorexecutes providers in fixed order (auth -> parameters -> request body).- Each provider applies rules to generate negative test cases expecting 400/401/403 status codes.
OutcomeAggregatorcollects provider results, merging test cases and errors.- If
includeValidCaseis enabled, the baseline valid case (with 2xx expected status) is prepended to the test list. TestCaseBudgetValidatorenforces per-operation limits on generated test cases.- Budgets and error handling determine whether the result is success, partial success, or failure. OpenAPI
webhooksare parsed but are currently out of scope for suite generation.
- Report outcomes:
GenerationReportcaptures successes, partials, and failures withGenerationErrormetadata.TestGenerationRunnerformats and logs the report via its reporter. - Emit artifacts:
TestGenerationEngine.createArtifactGeneratorbuilds a generator by id (test-suite-writerortemplate) and writes files. - Return result:
TestGenerationRunnerreturnsTestGenerationResult.SuccessorTestGenerationResult.Failurefor CLI/Gradle to handle appropriately.
Outputs¶
TestSuite: operation-level container (operationNameis the stable identifier).TestCase: individual scenario (request inputs + expected status/body).GenerationReport: aggregated suites + errors + summary (success/partial/failure/not-tested).
Core Abstractions¶
Provider-Rule Architecture Pattern¶
The generator separates what to vary (providers) from how to vary it (rules).
For each OpenAPI operation, ValidCaseBuilder constructs a baseline valid TestCase that includes only what is required: required parameters, first supported request body media type, and security values. For required parameters, schema resolution uses schema first; if schema is absent, it falls back to content schema. If both schema and content are defined for a parameter, schema is used and a warning is logged.
Providers decide what to vary for a given operation:
- Auth provider: derives auth-negative cases (missing/invalid credentials, scope variations).
- Parameter provider: derives parameter-negative cases (missing required params, schema violations).
- Request body provider: derives request-body-negative cases (missing body, schema violations).
Providers implement TestCaseProvider<T>, return Outcome<List<TestCase>>, and must be pure (no mutation of inputs). They are orchestrated in a fixed order by ProviderOrchestrator.
Rules decide how to vary a specific schema or security constraint:
SchemaValidationRuleproducesSequence<RuleValue>(lazy, deterministic). A provider turns eachRuleValueinto a negativeTestCase.AuthValidationRuleproducesSequence<TestCase>because auth variations often span multiple request fields and expected status codes (401/403).
Composition for array/object schemas is handled through ArrayItemSchemaValidationRule and ObjectItemSchemaValidationRule using a RuleContainer.
Registry: ManualRuleRegistry wires BuiltInRules plus any extra rules, ensuring deterministic ordering.
Generator Extensibility Model¶
- Artifact generators are created through
ArtifactGeneratorFactoryandArtifactGeneratorRegistryusing generator ids (GeneratorIds). - Built-in generators are registered explicitly by
BuiltInGenerators(currentlytest-suite-writer). - Feature modules implement
TestGenerationModuleto contribute:ArtifactGeneratorFactoryimplementationsSchemaValueProviderinstances (by id)- Additional
SimpleSchemaValidationRuleandAuthValidationRulerules
- CLI and Gradle pass modules explicitly (e.g.,
TemplateGeneratorModule,PatternSupportModule) to enable optional capabilities.
Configuration Resolution Chain¶
- Inputs:
- YAML config (
GeneratorConfig) loaded byGeneratorConfigLoader. - Environment overrides from CLI args or Gradle DSL (
TestGeneratorOverrides).
- YAML config (
- Merge:
TestGeneratorExecutionOptionsFactorymerges overrides over config (overrides win).- Module-specific settings are extracted via
ModuleSettingsExtractorbefore core settings parsing. TestGenerationSettingsandExampleValueSettingssupply defaults when values are missing.
- Defaults:
- CLI and Gradle insert the
patternexample-value provider into the default provider order beforeplain-string.
- CLI and Gradle insert the
- Note: The merge is deep for nested maps in
generatorOptionsandtestGenerationSettings. Collections are replaced (not merged), and map/non-map mismatches prefer the override value (no fail-fast).
Budget Controls¶
OpenAPI schemas can be deeply nested or contain combinatorial compositions (allOf/anyOf/oneOf). Budget controls cap worst-case behavior and keep generation tractable.
| Budget | Description |
|---|---|
maxSchemaDepth | Recursion limit when traversing schemas for validation |
maxMergedSchemaDepth | Recursion limit when merging composed schemas into a flattened view |
maxSchemaCombinations | Cap on composed-schema combinations (prevents anyOf/oneOf explosion) |
maxTestCasesPerOperation | Maximum test cases generated for a single operation |
maxErrors | Cap on collected errors when using COLLECT_ALL mode |
Defaults and types are documented in Distribution settings.
What happens when a budget is exceeded¶
Some budget violations are converted into structured generation errors (via provider boundaries), while others may stop generation for a specific operation depending on where the limit is enforced. When a budget is exceeded, the error includes the operation path and method, the current count vs. the limit, and suggested solutions (increase limit, simplify schema, or use ignore rules).
How to tune budgets¶
- Increase budgets when your schemas are legitimately complex and you need more coverage.
- Decrease budgets in CI when you prefer faster feedback.
- Consider using ignore rules for problematic operations instead of globally increasing limits.
For configuration keys, defaults, and where to set them (YAML vs CLI vs Gradle), see Distribution settings and YAML config.
Schema Processing¶
SchemaMerger¶
Handles OpenAPI schema composition (allOf, anyOf, oneOf):
- Flattens composed schemas into a single merged schema for validation.
- Preserves constraint semantics (e.g., tightest bounds win for min/max).
- Tracks recursion depth via
SchemaMergerOptions.maxMergedSchemaDepth. - Works with
CombinationBudgetto limit cartesian explosion.
Cycle Detection¶
SchemaStructureHashercomputes structural hashes to detect schema cycles.TestGenerationContextimplementations trackvisitedSchemaRefsandvisitedStructuresto prevent infinite loops.- Cycle detection is deterministic and does not rely on object identity.
Example Value Generation¶
Providers are tried in configured order until one produces a value:
- schema-example: Uses OpenAPI
exampleorexamplesfrom the schema. - pattern (pattern-support module): Generates values matching regex patterns.
- plain-string: Falls back to basic type-appropriate values.
ExampleValueSettings.providers controls the order. SchemaExampleValueGeneratorFactory wires the configured providers into a SchemaExampleValueGenerator.
Module Deep-Dives¶
Per-module responsibilities and entry points are documented under Modules. Start there for the authoritative "what lives where" view, then jump into the module page for the area you're working on.
Common starting points:
Cross-Cutting Concerns¶
Ignore Configuration¶
Test case and rule filtering is controlled via TestGenerationSettings:
- ignoreTestCases: Map of exact paths to operation/test case filters. Supports wildcard path (
*) and wildcard method (*); test case names are exact matches. - ignoreSchemaValidationRules: Set of fully qualified schema rule class names to skip.
- ignoreAuthValidationRules: Set of fully qualified auth rule class names to skip.
IgnoreConfigHandler applies filters during generation:
- Path matching: Filters operations by exact path or wildcard path.
- Method matching: Filters by HTTP method within matched paths (case-insensitive; wildcard supported).
- Test case matching: Filters individual test cases by exact name.
Filtering is applied deterministically after test case generation to ensure stable output.
GraalVM / Native Image Compatibility¶
- CLI uses the GraalVM Native Build Tools plugin (
:cli:nativeCompile). - Rule and generator wiring is explicit (
BuiltInRules,BuiltInGenerators,TestGenerationModule), avoiding reflection for discovery. - Reflection still exists in specific areas: the template generator uses Mustache's
ReflectionObjectHandler, and the Gradle plugin uses Kotlin reflection to copy settings. - The OpenAPI parser and Jackson can require reflection configuration for native images (see CLI documentation for agent-based config generation).
Determinism Guarantees¶
The generator produces stable outputs for the same inputs: the same set of test cases, in a stable order, with stable names and expected status codes.
- Providers: execute in a fixed order (auth → parameters → request body).
- Rules: registered and sorted deterministically (
BuiltInRules,ManualRuleRegistry,BuiltInGenerators); ignore filters are applied after sorting. - Modules:
TestGenerationEnginevalidates and sorts modules by id and then class name to ensure stable ordering. - Value providers:
ExampleValueSettings.providersdefines provider precedence; missing providers are logged, and fallback ordering is deterministic. - Writers:
TestSuiteWritersorts suite keys and test cases during merge to stabilize output; merges preserve deterministic ordering.
Common pitfalls¶
- Unordered maps/sets in configuration can cause unstable output if iterated directly. Prefer stable iteration order or explicit sorting at boundaries.
- Time/randomness must not affect test case names or values.
Error Handling Philosophy¶
- Errors as values: Most domain errors are captured as values, not thrown exceptions.
- Outcome type hierarchy:
Outcome.Success<T>: Operation completed with result.Outcome.PartialSuccess<T>: Operation produced results but also encountered errors.Outcome.Failure: Operation failed entirely with error list.
- Error aggregation:
OutcomeAggregatormerges provider results, accumulating test cases and errors. FinalOutcometype depends on whether any test cases were produced. - Error boundary:
runProviderSafelywraps provider execution, converting uncaught exceptions toOutcome.Failurewith meaningful context. - Error modes:
ErrorHandlingConfigcontrols behavior:ErrorMode.FAIL_FAST: Stop on first error.ErrorMode.COLLECT_ALL: Accumulate errors up tomaxErrorslimit (default 100).
- Budget violations:
BudgetExceededExceptionfrom provider logic (schema combinations/depth) is converted toGenerationErrorat provider boundaries.TestCaseBudgetValidatorexceptions currently propagate unless caught by the caller. - Error context: Every
GenerationErrorincludesErrorContextwith operation path, method, operationId, and optionally field/rule name for precise error location.