DB Tester Specification - Service Provider Interface (SPI)
SPI Overview
The framework uses Java ServiceLoader to decouple modules:
Design Principles
- API Independence: Test framework modules depend only on
db-tester-api. - Runtime Discovery: ServiceLoader loads core implementations at runtime.
- Extensibility: Custom implementations replace defaults when registered.
API Module SPIs
DataSetLoaderProvider
Provides the default DataSetLoader implementation.
Location: io.github.seijikohara.dbtester.api.spi.DataSetLoaderProvider
Interface:
public interface DataSetLoaderProvider {
DataSetLoader getLoader();
}Default Implementation: DefaultDataSetLoaderProvider in db-tester-core
Usage: Configuration.defaults() calls this provider to obtain the loader.
OperationProvider
Executes database operations on datasets.
Location: io.github.seijikohara.dbtester.api.spi.OperationProvider
Interface:
public interface OperationProvider {
void execute(
Operation operation,
TableSet tableSet,
DataSource dataSource,
TableOrderingStrategy tableOrderingStrategy,
TransactionMode transactionMode,
@Nullable Duration queryTimeout);
}Default Implementation: DefaultOperationProvider in db-tester-core
Parameters:
| Parameter | Type | Description |
|---|---|---|
operation | Operation | The database operation to execute |
tableSet | TableSet | The table set containing tables and rows |
dataSource | DataSource | The JDBC data source for connections |
tableOrderingStrategy | TableOrderingStrategy | Strategy for table processing order |
transactionMode | TransactionMode | Transaction behavior mode |
queryTimeout | @Nullable Duration | Query timeout, or null for no timeout |
Operations:
| Operation | Description |
|---|---|
NONE | No operation |
INSERT | Insert rows |
UPDATE | Update by primary key |
DELETE | Delete by primary key |
DELETE_ALL | Delete all rows |
UPSERT | Upsert (insert or update) |
TRUNCATE_TABLE | Truncate tables |
CLEAN_INSERT | Delete all then insert |
TRUNCATE_INSERT | Truncate then insert |
AssertionProvider
Performs database assertions for expectation verification.
Location: io.github.seijikohara.dbtester.api.spi.AssertionProvider
Interface:
public interface AssertionProvider {
// Core comparison methods
void assertEquals(TableSet expected, TableSet actual);
void assertEquals(TableSet expected, TableSet actual, AssertionFailureHandler failureHandler);
void assertEquals(Table expected, Table actual);
void assertEquals(Table expected, Table actual, Collection<String> additionalColumnNames);
void assertEquals(Table expected, Table actual, AssertionFailureHandler failureHandler);
// Comparison with column exclusion
void assertEqualsIgnoreColumns(TableSet expected, TableSet actual, String tableName,
Collection<String> ignoreColumnNames);
void assertEqualsIgnoreColumns(Table expected, Table actual,
Collection<String> ignoreColumnNames);
// Comparison with column strategies
void assertEqualsWithStrategies(Table expected, Table actual,
Collection<ColumnStrategyMapping> columnStrategies);
// SQL query-based comparison
void assertEqualsByQuery(TableSet expected, DataSource dataSource, String tableName,
String sqlQuery, Collection<String> ignoreColumnNames);
void assertEqualsByQuery(Table expected, DataSource dataSource, String tableName,
String sqlQuery, Collection<String> ignoreColumnNames);
}Default Implementation: DefaultAssertionProvider in db-tester-core
Key Methods:
| Method | Description |
|---|---|
assertEquals(TableSet, TableSet) | Compare two table sets |
assertEquals(Table, Table) | Compare two tables |
assertEqualsIgnoreColumns(...) | Compare while ignoring specific columns |
assertEqualsWithStrategies(...) | Compare with column-specific comparison strategies |
assertEqualsByQuery(...) | Compare query results against expected data |
Behavior:
- The provider compares expected and actual datasets or tables.
- The provider applies comparison strategies per column (STRICT, IGNORE, NUMERIC, and others).
- The provider collects all differences without fail-fast behavior.
- The provider outputs a human-readable summary with YAML details on mismatch.
See Error Handling - Validation Errors for output format details.
ExpectationProvider
Verifies database state against expected datasets.
Location: io.github.seijikohara.dbtester.api.spi.ExpectationProvider
Interface:
public interface ExpectationProvider {
// Basic verification
void verifyExpectation(TableSet expectedTableSet, DataSource dataSource);
// With column exclusion
default void verifyExpectation(TableSet expectedTableSet, DataSource dataSource,
Collection<String> excludeColumns);
// With column strategies
default void verifyExpectation(TableSet expectedTableSet, DataSource dataSource,
Collection<String> excludeColumns,
Map<String, ColumnStrategyMapping> columnStrategies);
// With row ordering
default void verifyExpectation(TableSet expectedTableSet, DataSource dataSource,
Collection<String> excludeColumns,
Map<String, ColumnStrategyMapping> columnStrategies,
RowOrdering rowOrdering);
}Default Implementation: DefaultExpectationProvider in db-tester-core
Methods:
| Method | Description |
|---|---|
verifyExpectation(TableSet, DataSource) | Basic database state verification |
verifyExpectation(..., excludeColumns) | Verify excluding specified columns |
verifyExpectation(..., columnStrategies) | Verify with column comparison strategies |
verifyExpectation(..., rowOrdering) | Verify with row ordering control |
Parameters:
| Parameter | Type | Description |
|---|---|---|
expectedTableSet | TableSet | The expected table set containing expected table data |
dataSource | DataSource | The database connection source for retrieving actual data |
excludeColumns | Collection<String> | Column names to exclude from comparison (case-insensitive) |
columnStrategies | Map<String, ColumnStrategyMapping> | Column comparison strategies keyed by column name |
rowOrdering | RowOrdering | Row comparison strategy (ORDERED or UNORDERED) |
Process:
- The provider iterates each table in the expected dataset and fetches actual data from the database.
- The provider filters actual data to include only columns present in the expected table.
- The provider applies column exclusions and comparison strategies.
- The provider compares filtered actual data against expected data.
- The provider throws
AssertionErrorif verification fails.
ScenarioNameResolver
Resolves scenario names from test method context.
Location: io.github.seijikohara.dbtester.api.scenario.ScenarioNameResolver
Interface:
public interface ScenarioNameResolver {
int DEFAULT_PRIORITY = 0;
ScenarioName resolve(Method testMethod);
default boolean canResolve(Method testMethod) {
return true;
}
default int priority() {
return DEFAULT_PRIORITY;
}
}Methods:
| Method | Return Type | Default | Description |
|---|---|---|---|
resolve(Method) | ScenarioName | - | Resolves scenario name from test method |
canResolve(Method) | boolean | true | Returns whether this resolver can handle the method |
priority() | int | 0 | Returns priority for resolver selection (higher = preferred) |
Implementations:
| Implementation | Module | Description |
|---|---|---|
JUnitScenarioNameResolver | db-tester-junit | Resolves from JUnit method name |
SpockScenarioNameResolver | db-tester-spock | Resolves from Spock feature name |
KotestScenarioNameResolver | db-tester-kotest | Resolves from Kotest test case name |
Resolution Logic:
- The framework sorts all registered resolvers by
priority()in descending order. - The framework queries each resolver via
canResolve(). - The framework selects the first resolver that returns
true. - The framework calls
resolve()to obtain the scenario name.
Core Module SPIs
FormatProvider
Parses dataset files in specific formats.
Location: io.github.seijikohara.dbtester.internal.format.spi.FormatProvider
Interface:
public interface FormatProvider {
FileExtension supportedFileExtension();
DataSet parse(Path directory);
}Methods:
| Method | Return Type | Description |
|---|---|---|
supportedFileExtension() | FileExtension | Returns the file extension without leading dot (for example, "csv") |
parse(Path) | TableSet | Parses all files in directory into a TableSet |
Implementations:
| Implementation | Extension | Delimiter |
|---|---|---|
CsvFormatProvider | .csv | Comma |
TsvFormatProvider | .tsv | Tab |
This internal SPI does not support external implementation.
ServiceLoader Registration
META-INF/services Files
db-tester-core:
# META-INF/services/io.github.seijikohara.dbtester.api.spi.DataSetLoaderProvider
io.github.seijikohara.dbtester.internal.loader.DefaultDataSetLoaderProvider
# META-INF/services/io.github.seijikohara.dbtester.api.spi.OperationProvider
io.github.seijikohara.dbtester.internal.spi.DefaultOperationProvider
# META-INF/services/io.github.seijikohara.dbtester.api.spi.AssertionProvider
io.github.seijikohara.dbtester.internal.spi.DefaultAssertionProvider
# META-INF/services/io.github.seijikohara.dbtester.api.spi.ExpectationProvider
io.github.seijikohara.dbtester.internal.spi.DefaultExpectationProvider
# META-INF/services/io.github.seijikohara.dbtester.internal.format.spi.FormatProvider
io.github.seijikohara.dbtester.internal.format.csv.CsvFormatProvider
io.github.seijikohara.dbtester.internal.format.tsv.TsvFormatProviderdb-tester-junit:
# META-INF/services/io.github.seijikohara.dbtester.api.scenario.ScenarioNameResolver
io.github.seijikohara.dbtester.junit.jupiter.spi.JUnitScenarioNameResolverdb-tester-spock:
# META-INF/services/io.github.seijikohara.dbtester.api.scenario.ScenarioNameResolver
io.github.seijikohara.dbtester.spock.spi.SpockScenarioNameResolverdb-tester-kotest:
# META-INF/services/io.github.seijikohara.dbtester.api.scenario.ScenarioNameResolver
io.github.seijikohara.dbtester.kotest.spi.KotestScenarioNameResolverJPMS Module Declarations
db-tester-api module-info.java:
module io.github.seijikohara.dbtester.api {
uses io.github.seijikohara.dbtester.api.spi.DataSetLoaderProvider;
uses io.github.seijikohara.dbtester.api.spi.OperationProvider;
uses io.github.seijikohara.dbtester.api.spi.AssertionProvider;
uses io.github.seijikohara.dbtester.api.spi.ExpectationProvider;
uses io.github.seijikohara.dbtester.api.scenario.ScenarioNameResolver;
}db-tester-core module-info.java:
module io.github.seijikohara.dbtester.core {
provides io.github.seijikohara.dbtester.api.spi.DataSetLoaderProvider
with io.github.seijikohara.dbtester.internal.loader.DefaultDataSetLoaderProvider;
provides io.github.seijikohara.dbtester.api.spi.OperationProvider
with io.github.seijikohara.dbtester.internal.spi.DefaultOperationProvider;
// ... other providers
}Custom Implementations
Custom DataSetLoader
To provide a custom dataset loader:
- Implement the
DataSetLoaderinterface:
public class CustomDataSetLoader implements DataSetLoader {
@Override
public List<TableSet> loadPreparationDataSets(TestContext context) {
// Custom loading logic
}
@Override
public List<TableSet> loadExpectationDataSets(TestContext context) {
// Custom loading logic
}
}- Register via
Configuration:
var config = Configuration.builder()
.loader(new CustomDataSetLoader())
.build();
DatabaseTestExtension.setConfiguration(context, config);Custom ScenarioNameResolver
To provide a custom scenario resolver:
- Implement
ScenarioNameResolver:
public class CustomScenarioNameResolver implements ScenarioNameResolver {
private static final int HIGH_PRIORITY = 100;
@Override
public ScenarioName resolve(Method testMethod) {
// Extract scenario name from method
}
@Override
public boolean canResolve(Method testMethod) {
// Return true for supported methods
}
@Override
public int priority() {
return HIGH_PRIORITY; // Higher priority than default resolvers
}
}- Register via ServiceLoader:
# META-INF/services/io.github.seijikohara.dbtester.api.scenario.ScenarioNameResolver
com.example.CustomScenarioNameResolverCustom FormatProvider
To support additional file formats (internal SPI):
- Implement
FormatProvider:
public class XmlFormatProvider implements FormatProvider {
@Override
public FileExtension supportedFileExtension() {
return new FileExtension("xml");
}
@Override
public TableSet parse(Path directory) {
// Parse all XML files in directory
}
}- Register via ServiceLoader:
# META-INF/services/io.github.seijikohara.dbtester.internal.format.spi.FormatProvider
com.example.XmlFormatProviderProvider Priority
The framework selects providers as follows when multiple providers exist:
| SPI | Selection |
|---|---|
DataSetLoaderProvider | First found |
OperationProvider | First found |
AssertionProvider | First found |
ExpectationProvider | First found |
ScenarioNameResolver | Sorted by priority(), first that canResolve() returns true |
FormatProvider | First matching supportedFileExtension() |
Related Specifications
- Overview - Framework purpose and key concepts
- Architecture - Module structure
- Configuration - Configuration classes
- Test Frameworks - Framework integration