| Type | Location | Suffix | Purpose |
|---|---|---|---|
| Unit tests | server/src/test/kotlin/ |
*Test.kt |
Service, store, and controller logic in isolation |
| Integration tests | integration-tests/src/test/kotlin/.../its/ |
*IT.kt |
Real AWS SDK v2 against a live Docker container |
Use @SpringBootTest with @MockitoBean for mocking. Extend the appropriate base class:
| Base Class | Use For |
|---|---|
ServiceTestBase |
Service-layer tests |
StoreTestBase |
Store-layer tests |
BaseControllerTest |
Controller slice tests (@WebMvcTest) |
Name the class under test iut (implementation under test); inject with @Autowired:
@SpringBootTest(classes = [ServiceConfiguration::class], webEnvironment = SpringBootTest.WebEnvironment.NONE)
@MockitoBean(types = [BucketService::class, MultipartService::class, MultipartStore::class])
internal class ObjectServiceTest : ServiceTestBase() {
@Autowired
private lateinit var iut: ObjectService
@Test
fun `should get object`() {
whenever(bucketStore.getBucketMetadata("bucket")).thenReturn(bucket)
whenever(objectStore.getObject(bucket, "key")).thenReturn(s3Object)
assertThat(iut.getObject("bucket", "key")).isEqualTo(s3Object)
}
}Extend S3TestBase for a pre-configured s3Client (AWS SDK v2). Accept TestInfo as a method parameter and use givenBucket(testInfo) for unique bucket names:
internal class MyFeatureIT : S3TestBase() {
@Test
fun `should perform operation`(testInfo: TestInfo) {
// Arrange
val bucketName = givenBucket(testInfo)
// Act
s3Client.putObject(
PutObjectRequest.builder().bucket(bucketName).key("key").build(),
RequestBody.fromString("content")
)
// Assert
val response = s3Client.getObject(
GetObjectRequest.builder().bucket(bucketName).key("key").build()
)
assertThat(response.readAllBytes().decodeToString()).isEqualTo("content")
}
}Access serviceEndpoint, serviceEndpointHttp, and serviceEndpointHttps from S3TestBase when needed.
See docs/KOTLIN.md for Kotlin naming conventions (backtick test names, internal visibility, naming patterns).
- Pattern: Arrange-Act-Assert
- Assertions: AssertJ (
assertThat(...)) — use specific matchers, not justisNotNull() - Error cases: Use AssertJ, not JUnit
assertThrows:assertThatThrownBy { s3Client.deleteBucket { it.bucket(bucketName) } } .isInstanceOf(AwsServiceException::class.java) .hasMessageContaining("Status Code: 409") - Independence: Each test creates its own resources — no shared state, UUID-based bucket names
make test # Unit tests only
make integration-tests # All integration tests
./mvnw verify -pl integration-tests -Dit.test=BucketIT # Specific class
./mvnw verify -pl integration-tests -Dit.test=BucketIT#shouldCreateBucket # Specific method
./mvnw test -pl server -DskipDocker # Skip DockerIntegration tests require Docker to be running.
- Docker not running: Integration tests will fail — start Docker first
- Port conflict: Check
lsof -i :9090 - Flaky test: Look for shared state or ordering dependencies
- Compilation error: Run
./mvnw clean install -DskipDocker -DskipTestsfirst
- Verify tests pass locally
- Cover both success and failure cases
- Keep tests independent (no shared state, UUID bucket names)
- Use specific assertions
- Run
make format