An open API service indexing awesome lists of open source software.

https://github.com/seijikohara/db-tester

Annotation-driven database testing framework for JVM. Load test data and verify database state using simple annotations, data files, and naming conventions.
https://github.com/seijikohara/db-tester

junit spock testing

Last synced: 4 months ago
JSON representation

Annotation-driven database testing framework for JVM. Load test data and verify database state using simple annotations, data files, and naming conventions.

Awesome Lists containing this project

README

          

# DB Tester

[![Test](https://github.com/seijikohara/db-tester/actions/workflows/test.yml/badge.svg)](https://github.com/seijikohara/db-tester/actions/workflows/test.yml)
[![Maven Central](https://img.shields.io/maven-central/v/io.github.seijikohara/db-tester-bom.svg)](https://search.maven.org/artifact/io.github.seijikohara/db-tester-bom)
[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
[![Docs](https://img.shields.io/badge/Docs-VitePress-646cff.svg)](https://seijikohara.github.io/db-tester/)


DB Tester Logo

A database testing framework for JUnit 6, Spock 2, and Kotest 6. Load CSV test data before tests and verify database state after tests using `@DataSet` and `@ExpectedDataSet` annotations.

**[Documentation](https://seijikohara.github.io/db-tester/)** · **[Maven Central](https://central.sonatype.com/artifact/io.github.seijikohara/db-tester-bom)** · **[Examples](examples/)**

---

## Quick Start

### JUnit

```java
@ExtendWith(DatabaseTestExtension.class)
class UserRepositoryTest {

@BeforeAll
static void setUp(ExtensionContext context) {
DataSource dataSource = createDataSource();
DatabaseTestExtension.getRegistry(context).registerDefault(dataSource);
}

@Test
@DataSet // Loads USERS.csv before test
@ExpectedDataSet // Verifies expected/USERS.csv after test
void shouldCreateUser() {
userRepository.create(new User("john", "john@example.com"));
}
}
```

### Spock

```groovy
@DatabaseTest
class UserRepositorySpec extends Specification {

@Shared
DataSourceRegistry dbTesterRegistry

def setupSpec() {
dbTesterRegistry = new DataSourceRegistry()
dbTesterRegistry.registerDefault(createDataSource())
}

@DataSet
@ExpectedDataSet
def "should create user"() {
when:
userRepository.create(new User("john", "john@example.com"))

then:
noExceptionThrown()
}
}
```

### Kotest

```kotlin
class UserRepositorySpec : AnnotationSpec() {

private val registry = DataSourceRegistry()

init {
extensions(DatabaseTestExtension(registryProvider = { registry }))
}

@BeforeAll
fun setupSpec() {
registry.registerDefault(createDataSource())
}

@Test
@DataSet
@ExpectedDataSet
fun `should create user`() {
userRepository.create(User("john", "john@example.com"))
}
}
```

### Dataset Files

```
src/test/resources/
└── com/example/UserRepositoryTest/
├── USERS.csv # Loaded before test
└── expected/
└── USERS.csv # Verified after test
```

**USERS.csv** (preparation):

```csv
ID,NAME,EMAIL
1,existing,existing@example.com
```

**expected/USERS.csv** (expectation):

```csv
ID,NAME,EMAIL
1,existing,existing@example.com
2,john,john@example.com
```

---

## Features

| Feature | Description |
|---------|-------------|
| Annotation-driven | Declarative test data management with `@DataSet` and `@ExpectedDataSet` |
| Convention-based | Automatic dataset discovery based on test class package and name |
| Scenario filtering | Share CSV files across tests using the `[Scenario]` column |
| Spring Boot integration | Automatic DataSource registration from ApplicationContext |
| Pure JDBC | No ORM or external testing framework dependencies |

---

## Requirements

| Component | Version |
|-----------|---------|
| Java | 21 or later |
| JUnit | 6 (for JUnit integration) |
| Spock | 2 with Groovy 5 (for Spock integration) |
| Kotest | 6 with Kotlin 2 (for Kotest integration) |
| Spring Boot | 4 (for Spring Boot integration) |

---

## Installation

Select a module based on your test framework:

| Use Case | Module |
|----------|--------|
| JUnit | `db-tester-junit` |
| JUnit with Spring Boot | `db-tester-junit-spring-boot-starter` |
| Spock | `db-tester-spock` |
| Spock with Spring Boot | `db-tester-spock-spring-boot-starter` |
| Kotest | `db-tester-kotest` |
| Kotest with Spring Boot | `db-tester-kotest-spring-boot-starter` |

All Modules

| Module | Description |
|--------|-------------|
| `db-tester-bom` | Bill of Materials for version management |
| `db-tester-api` | Public API (annotations, configuration, SPI) |
| `db-tester-core` | Internal implementation |
| `db-tester-junit` | JUnit extension |
| `db-tester-spock` | Spock extension |
| `db-tester-kotest` | Kotest extension |
| `db-tester-junit-spring-boot-starter` | Spring Boot auto-configuration for JUnit |
| `db-tester-spock-spring-boot-starter` | Spring Boot auto-configuration for Spock |
| `db-tester-kotest-spring-boot-starter` | Spring Boot auto-configuration for Kotest |

### Gradle

```kotlin
testImplementation(platform("io.github.seijikohara:db-tester-bom:VERSION"))
testImplementation("io.github.seijikohara:db-tester-junit")
```

### Maven

```xml



io.github.seijikohara
db-tester-bom
${db-tester.version}
pom
import

io.github.seijikohara
db-tester-junit
test

```

---

## Spring Boot Integration

Spring Boot starters automatically discover and register `DataSource` beans from the ApplicationContext. No manual registration is required.

### JUnit with Spring Boot

```java
@SpringBootTest
@ExtendWith(SpringBootDatabaseTestExtension.class)
class UserRepositoryTest {

@Autowired
private UserRepository userRepository;

@Test
@DataSet
@ExpectedDataSet
void shouldCreateUser() {
userRepository.save(new User("john", "john@example.com"));
}
}
```

### Spock with Spring Boot

```groovy
@SpringBootTest
@SpringBootDatabaseTest
class UserRepositorySpec extends Specification {

@Autowired
UserRepository userRepository

@DataSet
@ExpectedDataSet
def "should create user"() {
when:
userRepository.save(new User("john", "john@example.com"))

then:
noExceptionThrown()
}
}
```

### Kotest with Spring Boot

```kotlin
@SpringBootTest
class UserRepositorySpec : AnnotationSpec() {

@Autowired
private lateinit var userRepository: UserRepository

init {
extensions(SpringBootDatabaseTestExtension())
}

@Test
@DataSet
@ExpectedDataSet
fun `should create user`() {
userRepository.save(User("john", "john@example.com"))
}
}
```

### Configuration Properties

Configure via `application.properties`:

```properties
db-tester.enabled=true
db-tester.auto-register-data-sources=true
db-tester.convention.data-format=CSV
db-tester.convention.expectation-suffix=/expected
db-tester.operation.preparation=CLEAN_INSERT
```

See the [Configuration](https://seijikohara.github.io/db-tester/specs/04-configuration) documentation for all options.

---

## Usage Examples

### Scenario Filtering

Share CSV files across multiple tests using the `[Scenario]` column:

```csv
[Scenario],ID,NAME,EMAIL
shouldCreateUser,1,existing,existing@example.com
shouldUpdateUser,1,target,target@example.com
shouldDeleteUser,1,delete_me,delete@example.com
```

Each test method loads only rows matching its name.

### Custom Resource Location

Specify explicit resource locations instead of convention-based discovery:

```java
@DataSet(sources = @DataSetSource(resourceLocation = "custom/data"))
@ExpectedDataSet(sources = @DataSetSource(resourceLocation = "custom/expected"))
void testWithCustomLocation() { }
```

### Column Exclusion

Exclude columns (such as timestamps or auto-generated IDs) from verification:

**Per-dataset exclusion** via `@DataSetSource.excludeColumns`:

```java
@Test
@DataSet
@ExpectedDataSet(sources = @DataSetSource(
excludeColumns = {"CREATED_AT", "UPDATED_AT", "VERSION"}
))
void testWithExcludedColumns() {
userRepository.create(new User("john", "john@example.com"));
}
```

**Global exclusion** via `ConventionSettings.globalExcludeColumns`:

```java
@BeforeAll
static void setUp(ExtensionContext context) {
var config = Configuration.builder()
.conventions(ConventionSettings.builder()
.globalExcludeColumns(Set.of("CREATED_AT", "UPDATED_AT"))
.build())
.build();
DatabaseTestExtension.setConfiguration(context, config);
DatabaseTestExtension.getRegistry(context).registerDefault(dataSource);
}
```

**Spring Boot configuration**:

```properties
db-tester.convention.global-exclude-columns=CREATED_AT,UPDATED_AT,VERSION
```

Column names are case-insensitive. Per-dataset exclusions are combined with global exclusions.

---

## Configuration

### Operations

| Operation | Description |
|-----------|-------------|
| `NONE` | No database operation |
| `INSERT` | Insert rows |
| `UPDATE` | Update existing rows |
| `UPSERT` | Upsert (insert or update) |
| `DELETE` | Delete specified rows |
| `DELETE_ALL` | Delete all rows |
| `TRUNCATE_TABLE` | Truncate tables |
| `CLEAN_INSERT` | Delete all rows, then insert (default) |
| `TRUNCATE_INSERT` | Truncate, then insert |

```java
@DataSet(operation = Operation.INSERT)
```

### Data Formats

| Format | Extension |
|--------|-----------|
| CSV | `.csv` (default) |
| TSV | `.tsv` |

```java
ConventionSettings conventions = ConventionSettings.builder()
.dataFormat(DataFormat.TSV)
.build();
```

### Multiple DataSources

```java
DataSourceRegistry registry = DatabaseTestExtension.getRegistry(context);
registry.registerDefault(primaryDataSource);
registry.register("secondary", secondaryDataSource);
```

```java
@DataSet(sources = @DataSetSource(dataSourceName = "secondary"))
```

---

## Assertion Output

When verification fails, the framework reports differences in YAML format:

```yaml
summary:
status: FAILED
total_differences: 3
tables:
USERS:
differences:
- path: row_count
expected: 3
actual: 2
ORDERS:
differences:
- path: "row[0].STATUS"
expected: COMPLETED
actual: PENDING
column:
type: VARCHAR(50)
nullable: true
- path: "row[1].AMOUNT"
expected: 100.00
actual: 99.99
column:
type: "DECIMAL(10,2)"
```

### Output Structure

| Field | Description |
|-------|-------------|
| `summary.status` | `FAILED` when differences exist |
| `summary.total_differences` | Total count of differences |
| `tables..differences` | List of differences for each table |
| `path` | Location: `table_count`, `row_count`, or `row[N].COLUMN` |
| `expected` / `actual` | Expected and actual values |
| `column` | JDBC metadata when available |

---

## Troubleshooting

| Error | Cause | Solution |
|-------|-------|----------|
| `DataSetLoadException: Could not find dataset directory` | CSV path does not match test class | Verify directory structure matches package path |
| `DataSourceNotFoundException` | DataSource not registered | Register in `@BeforeAll` or use Spring Boot starter |

---

## Documentation

| Resource | Description |
|----------|-------------|
| [Technical Specifications](https://seijikohara.github.io/db-tester/) | Architecture, API, and configuration |
| [Examples](examples/) | Working test examples for all frameworks |

---

## License

MIT License. See [LICENSE](LICENSE) for details.