Hop JUnit is split into a parent aggregator and small, focused modules. You only pull in what your tests actually need.

Database modules

The database testing framework is grouped under the hop-junit-database-parent aggregator:

  • hop-junit-database-api - the engine-agnostic contract. It defines BaseDatabaseExtension (the backend SPI), the Descriptor/ManagedDatabase records, and the pure-JDK types @DbInit, @DbExpected, the DbComparison, DatasetFormat, DatasetOperation, DatabaseImplementation, JdbcNamed and JdbcConnectionSpec. It has no DBUnit and no Apache Hop dependency.

  • hop-junit-database-core - the @Database facade (and its repeatable container @Databases) and its DatabaseExtension. It resolves the backend through java.util.ServiceLoader (DatabaseExtensionRegistry), matching BaseDatabaseExtension.name() against DatabaseImplementation.current(). No backend type is referenced here, so the backend jars may be absent until the facade actually needs them.

  • hop-junit-database-testcontainers - the shared TestContainerDatabaseExtension base used by the container-backed backends (H2 has its own in-memory path, Oracle and Testcontainers derive from this). It depends only on the API module.

  • hop-junit-database-dataset - the DBUnit dataset engine (DbUnitEngine), the SQL script runner (SqlScriptRunner) and the resource resolver (DatasetResourceResolver). Both H2 and Oracle seed and verify through DbUnitEngine, so this module sits on top of the testcontainers base. It supports the YAML, XML, CSV and JSON dataset formats out of the box; XLS/XLSX (XlsDataSet) needs Apache POI on the test classpath and is documented in the API page.

  • hop-junit-database-h2 - the H2 backend (H2DatabaseExtension, @H2Database and its repeatable container @H2Databases), an in-memory Oracle-compatible H2 database, the H2DatabaseMode enum, and a JDBC driver (H2SqlRewriteDriver) that rewrites Oracle SQL for H2. Registered for the H2 implementation.

NOTE

To extend the built-in Oracle-to-H2 rewrites without changing this module, subclass H2SqlRewriteDriver, supply your function to super(rewriter, "jdbc:testoracleextended:h2:"), and register the subclass with java.sql.DriverManager before creating the extension (via META-INF/services/java.sql.Driver or an explicit DriverManager.registerDriver call). wrap(String) gives the H2 URL the driver accepts; unwrap(String) recovers the plain H2 URL. H2DatabaseExtension prefers one registered subclass accepting that prefix, rejects multiple such subclasses, and otherwise uses the built-in driver. The built-in driver's connection path also delegates to that registered subclass, so a previously registered built-in instance cannot silently bypass the custom rewrite function. JDBC interface unwrapping returns the rewriting wrapper; intentionally unwrapping an H2 implementation class exposes the underlying object and bypasses rewriting. The Oracle admin hook (OracleAdminProvisioner) lets consumers add owner-scoped provisioning (for example per-schema grants and synonyms) from their own project, keeping the extension generic.

NOTE
Oracle compatibility is partial: H2 tables are not physically partitioned and the rewrite removes PARTITION(...) on reads, so reads include the whole fixture table, not just the named partition. Partition-targeted writes are rejected instead of silently broadening their effect. Ordered XMLCAST(XMLAGG(XMLELEMENT(name, scalar_text) ORDER BY ...) AS CLOB) is supported for one scalar text argument, preserving surrounding RTRIM expressions, XML characters, nulls and large character results; it is not a general XML engine. The H2 _ROWID_ and ORA_HASH shim support repeatable reader bands for unchanged fixture rows but do not reproduce Oracle row identifiers or hash values. Whole-query application templates are not emulated by this driver. Single-named-source CONNECT BY queries with LEVEL termination are lowered to recursive CTEs, including qualified PRIOR columns, START WITH, post-hierarchy filters and duplicate source rows. This subset does not implement Oracle cycle detection, NOCYCLE, hierarchy path functions, sibling ordering, joined/derived sources, or binds inside the hierarchy block; unsupported shapes must not be treated as Oracle-equivalent. A missing or zero upper bound still emits the root, as on Oracle. Date arithmetic translation recognizes explicit Oracle date expressions and TO_DATE projection aliases, not arbitrary JDBC column types; date differences use numeric days at Oracle DATE second precision. Date-plus/minus-numeric CASE operands support searched, simple and nested cases when every result branch is a numeric literal, bind, TO_NUMBER, numeric cast, arithmetic over those forms, or NULL (including an omitted ELSE). Conditions and selectors are preserved, fractional offsets retain time-of-day, and unknown column/function result types or mixed date/text branches are left untranslated rather than assumed numeric. Date aliases are inferred from their own CTE projections, not unrelated projections sharing the same column name; this is conservative inference, not general SQL type resolution. DBMS_XMLGEN.GETXML executes a single SELECT on the active connection, serializes ROWSET/ROW XML, omits null columns, and returns null for no rows. It excludes writes, multiple statements and WITH queries, but does not sandbox user-installed SQL functions. XMLTABLE/PASSING, namespaces, arbitrary XML column names and complete Oracle XML serialization parity remain unsupported. In ORACLE_EXTENDED mode the ALL_TABLES, ALL_TAB_COLS, ALL_TAB_COLUMNS and ALL_CONSTRAINTS dictionaries are auto-installed as H2 views over INFORMATION_SCHEMA, so they always reflect the real schema (including SA.X FOR PUBLIC.X synonyms reported under the SA owner). The partition/app-metadata dictionaries (ALL_TAB_PARTITIONS, ALL_PART_TABLES, ALL_PART_KEY_COLUMNS, ALL_PART_INDEXES, ALL_IND_PARTITIONS and the USER_* index views) are installed as empty Oracle-typed scaffold tables that the consumer seeds with its own partition data; neither dictionary type should be re-created by the consumer. Schema-qualified references must be backed by real H2 schemas or synonyms; no schema prefix is silently removed. SHA-1 character input is encoded as UTF-8 for H2, which matches Oracle only when the Oracle database uses a compatible character encoding.
  • hop-junit-database-oracle - the Oracle backend (OracleDatabaseExtension, @OracleDatabase and its repeatable container @OracleDatabases), backed by a real Oracle 23ai Free Testcontainers container with per-schema isolation. The Oracle dependencies are declared unconditionally; you reference this module inside your own -Poracle profile. When admin-marked SQL scripts are declared, they run on the SYSTEM connection in declaration order, followed by any registered OracleAdminProvisioner.

Hop execution and coverage

  • hop-junit-core - the Hop testing engine and scope coverage:

  • @HopPipeline / @HopPipelineFixture / @HopPipelineExtension to run pipelines,

  • @HopWorkflow / @HopWorkflowFixture / @HopWorkflowExtension to run workflows,

  • @HopParameters / @HopParameter for execution parameters,

  • HopExecutionAssertions and the fluent assertSuccess() / assertFailure() / assertLogContains() API,

  • the coverage package under io.yupiik.hop.junit.hop.coverage: HopCoverageRuntime, the collectors, the post-processor, and the generic renderers. It also registers a LauncherSessionListener so coverage is aggregated and reported when the test session closes.

hop-junit-core intentionally depends only on hop-core and hop-engine, not on concrete transform/action plugin jars. Pipelines and workflows are deserialized at runtime, so you must add the hop-transform-* and hop-action-* artifacts that your .hpl/.hwf files actually use to your own test dependencies, for example org.apache.hop:hop-transform-tableinput or org.apache.hop:hop-action-sql. Their versions are managed by the hop-junit parent POM.

To keep the shipped classpath lean, hop-junit-core also excludes the heavy optional subsystems that hop-core/hop-engine transitively pull in: the entire Jetty web/REST/SOAP server stack (Jetty, Jersey, Metro, Jakarta WS/SOAP/WS APIs, including jetty-util), the Maven-resolver plugin-download layer, the CommonMark renderer, the gRPC remote-execution stack, Avro serialization (+ its Jackson dataformat), the Batik SVG renderer, the Rhino JavaScript engine, Snappy compression, the picocli CLI, the WebDAV VFS provider (commons-vfs2-jackrabbit2), JWT (nimbus-jose-jwt) and the legacy Apache HttpClient 4.x httpcore. Those are lazily loaded by Hop and never touched by the local pipeline engine that hop-junit-core exercises, so the local runtime is unaffected. The servlet API, io.smallrye:jandex and the Apache HttpClient 5 stack (httpclient5/httpcore5/httpcore5-h2) are deliberately retained because the Hop environment, plugin registry and VFS require them during bootstrap (jakarta.servlet.http.HttpServlet, org.jboss.jandex.IndexView in PluginRegistry, org.apache.hc.core5.ssl.TrustStrategy in HopVfs). The MinimalClasspathSafetyNetTest guards this: it boots the environment and fails if a trimmed stack is accidentally re-introduced or a retained class stops linking. A consumer who needs Hop's embedded web server, gRPC server, SVG rendering or WebDAV must add those artifacts explicitly.

  • hop-junit-documentation - this site.

Which modules do I need?

Typical classpaths
Use case Modules

Run a pipeline or workflow test with H2

hop-junit-core + hop-junit-database-api + hop-junit-database-core + hop-junit-database-h2, plus the hop-transform-*/hop-action-* jars your pipelines use

Same tests against real Oracle (add your own -Poracle profile)

the H2 set + hop-junit-database-oracle, and set database.default.implementation=ORACLE

Only the @Database facade, no Hop

hop-junit-database-api + hop-junit-database-core + (hop-junit-database-h2 or hop-junit-database-oracle)

Oracle module placement

hop-junit-database-oracle declares its Oracle dependencies (Testcontainers oracle-free, ojdbc11, hop-databases-oracle) unconditionally. It is you, the downstream consumer, who decides when those are present: put the hop-junit-database-oracle dependency inside your own -Poracle Maven profile and set database.default.implementation=ORACLE in that profile. Without the profile, only the pure H2 modules are on the classpath and H2 remains the default.

See the Quickstart for a concrete -Poracle profile.