Skip to content

Commit 9f71587

Browse files
authored
Merge pull request #2972 from adobe/copilot/refactor-testing-documentation
2 parents 6d47c4b + 38af87b commit 9f71587

File tree

7 files changed

+114
-95
lines changed

7 files changed

+114
-95
lines changed

.claude/skills/document/SKILL.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Read `AGENTS.md` (root + relevant module) before making changes.
1313
|------|----------|---------|
1414
| `README.md` | End users | Usage, configuration, operations table |
1515
| `CHANGELOG.md` | End users | Version history, breaking changes |
16+
| `docs/TESTING.md` | Contributors / AI agents | Testing strategy, base classes, patterns, commands |
1617
| `AGENTS.md` (root + modules) | AI agents | Architecture, conventions, guardrails |
1718
| `.github/CONTRIBUTING.md` | Contributors | Dev setup, code reviews |
1819

.claude/skills/test/SKILL.md

Lines changed: 3 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,9 @@ description: Write, update, or fix tests. Use when asked to test code, create te
55

66
# Test Skill — S3Mock
77

8-
Read `AGENTS.md` (root + relevant module) before writing tests — especially the Testing, DO/DON'T sections and base classes.
8+
Read **[docs/TESTING.md](../../../docs/TESTING.md)** and `AGENTS.md` (root + relevant module) before writing tests — they define test types, base classes, naming conventions, and running commands.
99

10-
## Test Types
11-
12-
### Unit Tests (`*Test.kt`) — `server/src/test/kotlin/.../`
13-
- `@SpringBootTest` with `@MockitoBean` for mocking (see AGENTS.md for details)
14-
- Extend the appropriate base class: `ServiceTestBase`, `StoreTestBase`, or `BaseControllerTest`
15-
- Name the class under test `iut`
16-
- Use `@Autowired` for the class under test
17-
18-
### Integration Tests (`*IT.kt`) — `integration-tests/src/test/kotlin/.../its/`
19-
- Extend `S3TestBase` for pre-configured `s3Client` (AWS SDK v2)
20-
- Use `givenBucket(testInfo)` for unique bucket names
21-
- Tests run against Docker container — Docker must be running
22-
23-
## Key Conventions (see AGENTS.md for full list)
10+
## Key Conventions (from AGENTS.md)
2411

2512
- **Naming**: Backtick names: `` fun `should create bucket successfully`() ``
2613
- **Pattern**: Arrange-Act-Assert
@@ -29,24 +16,9 @@ Read `AGENTS.md` (root + relevant module) before writing tests — especially th
2916
- **Error cases**: `assertThatThrownBy { ... }.isInstanceOf(AwsServiceException::class.java)`
3017
- **Visibility**: `internal class`
3118

32-
## Running Tests
33-
34-
```bash
35-
./mvnw test -pl server # Unit tests
36-
./mvnw verify -pl integration-tests # All integration tests
37-
./mvnw verify -pl integration-tests -Dit.test=BucketIT # Specific class
38-
./mvnw test -pl server -DskipDocker # Skip Docker
39-
```
40-
41-
## Troubleshooting
42-
43-
- **Docker not running**: Integration tests require Docker
44-
- **Port conflict**: Check `lsof -i :9090`
45-
- **Flaky test**: Check for shared state or ordering dependencies
46-
- **Compilation error**: Run `./mvnw clean install -DskipDocker -DskipTests` first
47-
4819
## Checklist
4920

21+
- [ ] Read `docs/TESTING.md` and root + module `AGENTS.md`
5022
- [ ] Tests pass locally
5123
- [ ] Both success and failure cases covered
5224
- [ ] Tests are independent (no shared state, UUID bucket names)

AGENTS.md

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -107,15 +107,7 @@ See `server/AGENTS.md` for details.
107107

108108
## Testing
109109

110-
- Unit tests: `@SpringBootTest` with `@MockitoBean`, suffix `Test`
111-
- Integration tests: Real AWS SDK v2 against Docker container, suffix `IT`
112-
- Test independence: Each test self-contained
113-
- Name the class under test **`iut`** (implementation under test): `private lateinit var iut: ObjectService`
114-
- **Base classes** — always extend the appropriate base:
115-
- `ServiceTestBase` for service tests
116-
- `StoreTestBase` for store tests
117-
- `BaseControllerTest` for controller tests
118-
- `S3TestBase` for integration tests
110+
See **[docs/TESTING.md](docs/TESTING.md)** for the full testing strategy, base classes, patterns, and commands.
119111

120112
## Build
121113

docs/TESTING.md

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# Testing Strategy — S3Mock
2+
3+
## Test Types
4+
5+
| Type | Location | Suffix | Purpose |
6+
|------|----------|--------|---------|
7+
| Unit tests | `server/src/test/kotlin/` | `*Test.kt` | Service, store, and controller logic in isolation |
8+
| Integration tests | `integration-tests/src/test/kotlin/.../its/` | `*IT.kt` | Real AWS SDK v2 against a live Docker container |
9+
10+
## Unit Tests
11+
12+
Use `@SpringBootTest` with `@MockitoBean` for mocking. Extend the appropriate base class:
13+
14+
| Base Class | Use For |
15+
|---|---|
16+
| `ServiceTestBase` | Service-layer tests |
17+
| `StoreTestBase` | Store-layer tests |
18+
| `BaseControllerTest` | Controller slice tests (`@WebMvcTest`) |
19+
20+
Name the class under test **`iut`** (implementation under test); inject with `@Autowired`:
21+
22+
```kotlin
23+
@SpringBootTest(classes = [ServiceConfiguration::class], webEnvironment = SpringBootTest.WebEnvironment.NONE)
24+
@MockitoBean(types = [BucketService::class, MultipartService::class, MultipartStore::class])
25+
internal class ObjectServiceTest : ServiceTestBase() {
26+
@Autowired
27+
private lateinit var iut: ObjectService
28+
29+
@Test
30+
fun `should get object`() {
31+
whenever(bucketStore.getBucketMetadata("bucket")).thenReturn(bucket)
32+
whenever(objectStore.getObject(bucket, "key")).thenReturn(s3Object)
33+
assertThat(iut.getObject("bucket", "key")).isEqualTo(s3Object)
34+
}
35+
}
36+
```
37+
38+
## Integration Tests
39+
40+
Extend `S3TestBase` for a pre-configured `s3Client` (AWS SDK v2). Accept `TestInfo` as a method parameter and use `givenBucket(testInfo)` for unique bucket names:
41+
42+
```kotlin
43+
internal class MyFeatureIT : S3TestBase() {
44+
@Test
45+
fun `should perform operation`(testInfo: TestInfo) {
46+
// Arrange
47+
val bucketName = givenBucket(testInfo)
48+
49+
// Act
50+
s3Client.putObject(
51+
PutObjectRequest.builder().bucket(bucketName).key("key").build(),
52+
RequestBody.fromString("content")
53+
)
54+
55+
// Assert
56+
val response = s3Client.getObject(
57+
GetObjectRequest.builder().bucket(bucketName).key("key").build()
58+
)
59+
assertThat(response.readAllBytes().decodeToString()).isEqualTo("content")
60+
}
61+
}
62+
```
63+
64+
Access `serviceEndpoint`, `serviceEndpointHttp`, and `serviceEndpointHttps` from `S3TestBase` when needed.
65+
66+
## Conventions
67+
68+
- **Naming**: Backtick names with descriptive sentences — `` fun `should create bucket successfully`() ``
69+
- **Visibility**: Mark test classes `internal`
70+
- **Pattern**: Arrange-Act-Assert
71+
- **Assertions**: AssertJ (`assertThat(...)`) — use specific matchers, not just `isNotNull()`
72+
- **Error cases**: Use AssertJ, not JUnit `assertThrows`:
73+
```kotlin
74+
assertThatThrownBy { s3Client.deleteBucket { it.bucket(bucketName) } }
75+
.isInstanceOf(AwsServiceException::class.java)
76+
.hasMessageContaining("Status Code: 409")
77+
```
78+
- **Independence**: Each test creates its own resources — no shared state, UUID-based bucket names
79+
- **Legacy names**: Refactor `testSomething` camelCase names to backtick style when touching existing tests
80+
81+
## Running Tests
82+
83+
```bash
84+
./mvnw test -pl server # Unit tests only
85+
./mvnw verify -pl integration-tests # All integration tests
86+
./mvnw verify -pl integration-tests -Dit.test=BucketIT # Specific class
87+
./mvnw verify -pl integration-tests -Dit.test=BucketIT#shouldCreateBucket # Specific method
88+
./mvnw test -pl server -DskipDocker # Skip Docker
89+
```
90+
91+
> Integration tests require Docker to be running.
92+
93+
## Troubleshooting
94+
95+
- **Docker not running**: Integration tests will fail — start Docker first
96+
- **Port conflict**: Check `lsof -i :9090`
97+
- **Flaky test**: Look for shared state or ordering dependencies
98+
- **Compilation error**: Run `./mvnw clean install -DskipDocker -DskipTests` first
99+
100+
## Checklist
101+
102+
- [ ] Tests pass locally
103+
- [ ] Both success and failure cases covered
104+
- [ ] Tests are independent (no shared state, UUID bucket names)
105+
- [ ] Assertions are specific
106+
- [ ] Run `./mvnw ktlint:format`

integration-tests/AGENTS.md

Lines changed: 1 addition & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
> Inherits all conventions from the [root AGENTS.md](../AGENTS.md). Below are module-specific additions only.
44
5-
Integration tests verifying S3Mock with real AWS SDK v2 clients.
5+
Integration tests verifying S3Mock with real AWS SDK v2 clients. See **[docs/TESTING.md](../docs/TESTING.md)** for the full testing strategy, conventions, patterns, and running commands.
66

77
## Structure
88

@@ -23,45 +23,9 @@ Extend `S3TestBase` for access to:
2323
- Extend **`S3TestBase`** for all integration tests
2424
- Accept **`testInfo: TestInfo`** parameter in test methods for unique resource naming
2525
- Use **`givenBucket(testInfo)`** for bucket creation — don't create your own helper
26-
- Use **Arrange-Act-Assert** pattern consistently
2726
- Verify HTTP codes, headers (ETag, Content-Type), XML/JSON bodies, and error responses
2827
- DON'T mock AWS SDK clients — use actual SDK clients against S3Mock
2928

30-
## Test Pattern
31-
32-
```kotlin
33-
internal class MyFeatureIT : S3TestBase() {
34-
@Test
35-
fun `should perform operation`(testInfo: TestInfo) {
36-
// Arrange
37-
val bucketName = givenBucket(testInfo)
38-
39-
// Act
40-
s3Client.putObject(
41-
PutObjectRequest.builder().bucket(bucketName).key("key").build(),
42-
RequestBody.fromString("content")
43-
)
44-
45-
// Assert
46-
val response = s3Client.getObject(
47-
GetObjectRequest.builder().bucket(bucketName).key("key").build()
48-
)
49-
assertThat(response.readAllBytes().decodeToString()).isEqualTo("content")
50-
}
51-
}
52-
```
53-
54-
## Common Patterns
55-
56-
**Multipart**: Initiate → Upload parts → Complete → Verify
57-
58-
**Errors** (use AssertJ, not JUnit `assertThrows`):
59-
```kotlin
60-
assertThatThrownBy { s3Client.deleteBucket { it.bucket(bucketName) } }
61-
.isInstanceOf(AwsServiceException::class.java)
62-
.hasMessageContaining("Status Code: 409")
63-
```
64-
6529
## Running
6630

6731
```bash

server/AGENTS.md

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -47,23 +47,7 @@ server/src/main/kotlin/com/adobe/testing/s3mock/
4747

4848
## Testing
4949

50-
Service and store unit tests use `@SpringBootTest` with `@MockitoBean`, while controller tests are slice tests using `@WebMvcTest` with `@MockitoBean` and `BaseControllerTest`. Extend the appropriate base class (`ServiceTestBase`, `StoreTestBase`, `BaseControllerTest`):
51-
52-
```kotlin
53-
@SpringBootTest(classes = [ServiceConfiguration::class], webEnvironment = SpringBootTest.WebEnvironment.NONE)
54-
@MockitoBean(types = [BucketService::class, MultipartService::class, MultipartStore::class])
55-
internal class ObjectServiceTest : ServiceTestBase() {
56-
@Autowired
57-
private lateinit var iut: ObjectService
58-
59-
@Test
60-
fun `should get object`() {
61-
whenever(bucketStore.getBucketMetadata("bucket")).thenReturn(bucket)
62-
whenever(objectStore.getObject(bucket, "key")).thenReturn(s3Object)
63-
assertThat(iut.getObject("bucket", "key")).isEqualTo(s3Object)
64-
}
65-
}
66-
```
50+
See **[docs/TESTING.md](../docs/TESTING.md)** for the full strategy. Service and store tests use `@SpringBootTest` with `@MockitoBean`; controller tests use `@WebMvcTest` with `@MockitoBean` and `BaseControllerTest`. Always extend the appropriate base class (`ServiceTestBase`, `StoreTestBase`, `BaseControllerTest`).
6751

6852
## Configuration
6953

testsupport/AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
> Inherits all conventions from the [root AGENTS.md](../AGENTS.md). Below are module-specific additions only.
44
5-
Test framework integrations for using S3Mock: JUnit 5, Testcontainers, TestNG.
5+
Test framework integrations for using S3Mock: JUnit 5, Testcontainers, TestNG. See **[docs/TESTING.md](../docs/TESTING.md)** for the overall testing strategy.
66

77
> **Deprecation Notice (6.x):** The JUnit 5, TestNG, and all direct-integration modules will be
88
> removed in S3Mock 6.x. Testcontainers will become the only officially supported testing approach.

0 commit comments

Comments
 (0)