A toolkit for testing Java cloud interactions against local emulators rather than real cloud accounts.
The core defines small, provider-neutral capability interfaces. Provider adapters implement them and keep the vendor SDKs to themselves. Tests written against the core do not name a cloud provider.
What it does and does not do. A test asks for a BlobStorage or a Queue and drives it directly, so you can assert that your code puts the right bytes in the right bucket. It does not configure your application under test: nothing in the provider-neutral API exposes an endpoint or a credential, so you cannot point a Spring Boot, Quarkus or Micronaut context at the emulator this library starts. For that today you would use Testcontainers' LocalStack module directly. Closing the gap is Epic 8 and needs an SPI decision first.
Status: early-stage private alpha. AWS is the only provider, and only in emulator mode.
| groupId | com.enrichmeai |
| artifactId | enrich-test-api (parent) |
| version | 0.3.0-alpha1-private.1 |
| modules | test-core, test-cloud-aws, test-feature |
The Java packages are still org.deveasy.*. Renaming them is a breaking API change
across every source file and is deliberately not part of the coordinate rebrand.
| Module | Contents |
|---|---|
test-core |
Provider-agnostic capability interfaces, TestCloudConfig, the CloudAdapter SPI, and the JUnit 5 @WithCloud extension. No vendor SDKs. |
test-cloud-aws |
AWS adapter: AWS SDK v2 plus Testcontainers/LocalStack. Implements BlobStorage (S3), Queue (SQS), PubSub (SNS+SQS) and NoSqlTable (DynamoDB). |
test-feature |
Provider-neutral Cucumber glue and JUnit Platform suites. No main sources; everything lives under src/test. |
Key directories:
test-core/src/main/java/org/deveasy/test/core/cloud/core API, config, capabilitiestest-feature/src/test/java/org/deveasy/test/feature/cloud/Cucumber glue, suites, scenario statetest-feature/src/test/resources/features/provider-neutral feature filestest-cloud-aws/src/main/java/org/deveasy/test/cloud/aws/AWS adapter and client wiringdocs/adr/architecture decision records
Capability interfaces live in test-core: BlobStorage, Queue, PubSub, NoSqlTable. Runtime configuration is a single immutable TestCloudConfig carrying provider, mode, region and overrides.
Adapters are discovered with java.util.ServiceLoader. An adapter implements org.deveasy.test.core.cloud.spi.CloudAdapter and registers under META-INF/services/. Putting test-cloud-aws on the classpath is enough for the AWS adapter to be found. See ADR 0001.
Emulators come first. The AWS adapter starts a LocalStack container through Testcontainers, so a test run needs Docker but no cloud credentials. See ADR 0002.
Requirements:
- Java 17
- Maven 3.9+
- Docker running, for the emulator-backed tests
Build and run everything:
mvn -B verifyThat runs unit tests, the LocalStack integration tests, the Cucumber suites, and every quality gate. There are no skip flags to add.
To run only the AWS module:
mvn -B -pl test-cloud-aws -am verifyTestcontainers pulls localstack/localstack:3.8 on the first run, which is roughly 1.3 GB.
Select a provider and mode from a feature file:
Given cloud provider is "aws"
And cloud mode is "emulator"
And cloud region is "eu-west-1"| Suite | Where | Count |
|---|---|---|
| Unit tests (Surefire) | test-core, test-feature | 8 |
| Integration tests (Failsafe) | test-cloud-aws, against LocalStack | 6 |
| Cucumber scenarios (Failsafe) | test-feature, against LocalStack | 7 |
The *IT and *Suite classes are picked up by maven-failsafe-plugin, configured in the root POM. Surefire's default includes do not match either naming pattern.
These are the gates as the POM enforces them today.
| Gate | Tool | Enforced at | Behaviour |
|---|---|---|---|
| Formatting | Spotless, google-java-format | verify |
Fails on any deviation. mvn spotless:apply fixes it. |
| Style | Checkstyle 3.6.0 | verify |
Fails on violations. Rules are import hygiene only: unused, redundant and star imports. Main sources only. |
| Build hygiene | Maven Enforcer | validate |
Java 17 or above, and full dependency convergence. |
| Coverage | JaCoCo | verify |
Per-module floors, see below. |
| Static analysis | Error Prone | -Perrorprone only |
Findings at ERROR fail the build. Currently only WARN findings exist. |
| Supply chain | OWASP Dependency-Check | -Powasp only |
Not part of a plain verify; it needs an NVD API key. |
JaCoCo floors are set per module to the ratios each module actually reaches. They are a ratchet against regression, not a target that has been met.
| Module | Line covered | Line floor | Branch covered | Branch floor |
|---|---|---|---|---|
| test-core | 104/178, 0.58 | 0.58 | 14/46, 0.30 | 0.30 |
| test-cloud-aws | 344/516, 0.66 | 0.66 | 81/228, 0.35 | 0.35 |
| test-feature | no main sources | none | no main sources | none |
The project target remains line 0.80 and branch 0.70. Neither module meets it. Raise the floors as tests are added; do not lower them.
GitHub Actions, two workflows, both triggered on pushes and pull requests against main.
build.ymlrunsmvn -B verifyon an Ubuntu runner with Docker, then uploads the Surefire and Failsafe reports and the JaCoCo HTML.quality-gates.ymlruns Spotless and Checkstyle, then Enforcer, then Error Prone, then the OWASP audit.
The OWASP job is skipped unless an NVD_API_KEY secret is present on the repository, because Dependency-Check cannot build its database without one. The job annotates the run when it skips.
Both workflows build on JDK 17, matching the release target, so a contributor's local mvn -B verify is the same build CI runs. An automated upgrade proposed moving everything to 21; it was declined, because nothing in the code uses a feature later than 17 and raising the target only narrows who can adopt the library. The conditions under which that would be revisited — after Epic 1, as a 17 + 21 matrix — are recorded in ADR 0007.
BMAD Method 6.12.0 is installed in this repo. The planning artifacts live in docs/specs/:
| Document | Contents |
|---|---|
| product-brief.md | Problem, solution, users, scope |
| PRD.md | Glossary, user journeys, FR-1 to FR-10, non-goals, open questions |
| architecture.md | Ports-and-adapters spine, AD-1 to AD-9, stack, dependency-direction diagram |
| epics.md | Eight epics of work not yet done, including the coverage gap and the framework-support gap |
| implementation-readiness.md | Which stories can be built now, and the decisions that block the rest |
| implementation/ | Expanded story files for the unblocked work, and sprint-status.yaml |
Eight of the twenty-nine stories are expanded to file-and-line detail and can be picked up
today. Three are genuine decisions only the maintainer can settle — the second provider, the
NVD_API_KEY owner and audit policy, and the shape of the connection accessor — and are left
at epic grain rather than expanded into invented answers.
A further three read as decisions but are not, because the library is unpublished with no
consumers: the package rename, removing CloudMode.LIVE, and removing SECRETS and KMS
were only ever weighed against a breaking-change cost that does not exist yet. They are free
work. Only the package rename has a deadline, since publishing makes it permanent.
implementation-readiness.md has the full list.
The specs describe the tree as it is, with gaps named as gaps. Architecture decision
records remain in docs/adr/.
Nothing here is published to any registry, and this section is what would have to be true first. It is not a roadmap with dates; it is the list of things that are currently wrong or undecided, kept honest against the backlog in epics.md.
Defects that must be fixed. The library misbehaves in exactly the case it is aimed at:
several test classes in one run. The emulator container is shared for the whole JVM, the
JUnit extension never releases topics or tables, and ensureTable returns without checking
that an existing table's key schema matches the one requested — so a second test class can
silently inherit the first's table, and the failure surfaces later in putItem, which has no
catch, as a raw SDK ValidationException. The test that breaks is not the test that caused
it. That is Epic 7, and it is the clearest blocker.
Free work, cheap now and not later. Nothing is published and there are no consumers, so
several things usually treated as breaking changes cost nothing today. Rename the Java packages
from org.deveasy.* to match the com.enrichmeai coordinates (Story 2.1) — an afternoon now,
and permanent after publication, since no later change rescues a consumer's import statements.
Take CloudMode.LIVE out until it works (Story 4.1) and drop SECRETS and KMS from
CloudServiceType (Story 6.4); both are declared but unimplemented, and removing them breaks
nobody.
Housekeeping. The version is 0.3.0-alpha1-private.1, which cannot go to a public
registry as it stands, and the POM still carries OSSRH publishing configuration pointing at a
decommissioned host, which would have to go before any modern publishing setup arrives
(Story 6.3).
A product question rather than a technical one. The framework gap described above is Epic 8. It is not blocked by compatibility — an accessor can be added at any time — but with no users the useful question is not what breaks existing consumers, it is what makes a first install worth doing. Most Java engineers testing cloud-backed services are on Spring Boot, and today they would find they cannot point their application context at the emulator and go back to Testcontainers. Whether that makes it a launch feature is a judgement, not a deduction.
Known and deliberately not blocking. Coverage sits below its target, and CloudExtension
— the injection path every user touches — is the thinnest part of it at 12 of 42 branches
covered (Epic 1). One provider, emulator only, so the portability claim is a design intention
rather than a demonstrated property (Epic 3).
The full readiness assessment, including the decisions that are open, is in implementation-readiness.md.
See CONTRIBUTING.md. In short: branch, keep mvn -B verify green, use conventional commit messages, and sign off your commits.
Apache License 2.0. See LICENSE.