upgrade to Spring Boot 4.2.1
This commit is contained in:
@@ -48,7 +48,7 @@ All production code lives under `de.hoennig.gittally`. Commands are in the `comm
|
||||
|
||||
## Testing Conventions
|
||||
|
||||
Tests use **Kotest `FunSpec`** style. `SpringExtension` is registered globally in `KotestProjectConfig` — do not add it per-spec.
|
||||
Tests use **Kotest `FunSpec`** style. `SpringExtension` is registered globally in `io.kotest.provided.ProjectConfig` — do not add it per-spec.
|
||||
|
||||
```kotlin
|
||||
class MyTest : FunSpec() {
|
||||
@@ -63,7 +63,21 @@ Use `shouldBe`, `shouldNotBe`, `shouldThrow` etc. from `io.kotest.matchers`.
|
||||
|
||||
### Mocking in Spring Slice Tests
|
||||
|
||||
`springmockk` is not compatible with the target Spring Boot version (4.0). The `@MockkBean` annotation is unavailable. Use `@TestConfiguration` to register MockK mocks as Spring beans:
|
||||
Use `@MockkBean` from `springmockk` to inject MockK mocks into the Spring context:
|
||||
|
||||
```kotlin
|
||||
@WebMvcTest(SomeController::class)
|
||||
class SomeControllerTest : FunSpec() {
|
||||
@MockkBean
|
||||
lateinit var someService: SomeService
|
||||
init {
|
||||
beforeEach { clearMocks(someService) }
|
||||
// full MockK syntax: every { } / verify { }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Alternatively, register mocks via `@TestConfiguration` without the springmockk dependency:
|
||||
|
||||
```kotlin
|
||||
@WebMvcTest(SomeController::class)
|
||||
@@ -87,7 +101,5 @@ Pure unit tests (no Spring context) use MockK directly without any Spring wiring
|
||||
All major decisions are in `docs/adrs/`. Run `adr-status` (after `source .envrc`) for a one-line summary of each. Decisions in force:
|
||||
|
||||
- **Test framework**: Kotest + MockK + WireMock + Testcontainers (ADR 0001)
|
||||
- **Gradle**: 8.14 target (currently 8.8; upgrade pending Spring Boot 4.0 migration) (ADR 0002)
|
||||
- **Spring Boot**: 4.0 target (currently 3.3.4 — pending upgrade; 3.3.x is EOL) (ADR 0003)
|
||||
|
||||
The Spring Boot and Gradle upgrades are open work items documented in the ADRs. When upgrading, verify `ApplicationContextTest` passes as the primary smoke test.
|
||||
- **Gradle**: 8.14.5 (ADR 0002)
|
||||
- **Spring Boot**: 4.0.6 (ADR 0003)
|
||||
|
||||
+15
-15
@@ -1,11 +1,11 @@
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
plugins {
|
||||
id("org.springframework.boot") version "3.3.4"
|
||||
id("io.spring.dependency-management") version "1.1.6"
|
||||
id("org.jlleitschuh.gradle.ktlint") version "12.1.1"
|
||||
kotlin("jvm") version "1.9.25"
|
||||
kotlin("plugin.spring") version "1.9.25"
|
||||
id("org.springframework.boot") version "4.0.6"
|
||||
id("io.spring.dependency-management") version "1.1.7"
|
||||
id("org.jlleitschuh.gradle.ktlint") version "14.2.0"
|
||||
kotlin("jvm") version "2.2.0"
|
||||
kotlin("plugin.spring") version "2.2.0"
|
||||
}
|
||||
|
||||
group = "de.hoennig"
|
||||
@@ -29,26 +29,26 @@ dependencies {
|
||||
testImplementation("org.springframework.boot:spring-boot-starter-test")
|
||||
|
||||
// Kotest
|
||||
testImplementation("io.kotest:kotest-runner-junit5:5.9.1")
|
||||
testImplementation("io.kotest:kotest-assertions-core:5.9.1")
|
||||
testImplementation("io.kotest.extensions:kotest-extensions-spring:1.3.0")
|
||||
testImplementation("io.kotest:kotest-runner-junit5:6.1.5")
|
||||
testImplementation("io.kotest:kotest-assertions-core:6.1.5")
|
||||
testImplementation("io.kotest:kotest-extensions-spring:6.1.5")
|
||||
|
||||
// MockK
|
||||
testImplementation("io.mockk:mockk:1.13.11")
|
||||
testImplementation("com.ninja-squad:springmockk:4.0.2")
|
||||
testImplementation("com.ninja-squad:springmockk:5.0.1")
|
||||
|
||||
// WireMock
|
||||
testImplementation("org.wiremock.integrations:wiremock-spring-boot:3.1.0")
|
||||
testImplementation("org.wiremock.integrations:wiremock-spring-boot:4.2.1")
|
||||
|
||||
// Testcontainers (versions managed by Spring Boot BOM)
|
||||
testImplementation("org.springframework.boot:spring-boot-testcontainers")
|
||||
testImplementation("org.testcontainers:testcontainers")
|
||||
}
|
||||
|
||||
tasks.withType<KotlinCompile> {
|
||||
kotlinOptions {
|
||||
freeCompilerArgs += "-Xjsr305=strict"
|
||||
jvmTarget = "21"
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
freeCompilerArgs.addAll("-Xjsr305=strict")
|
||||
jvmTarget = JvmTarget.JVM_21
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
**Status:**
|
||||
- proposed: 2026-06-09
|
||||
- accepted: -
|
||||
- accepted: 2026-06-09
|
||||
- rejected: -
|
||||
- superseded: -
|
||||
|
||||
**Decision [proposed]:** Gradle 8.14 — minimum required by Spring Boot 4.0; Gradle 9 deferred until openapi-generator and ktlint-gradle confirm compatibility.
|
||||
**Decision [accepted]:** Gradle 8.14.5 — minimum required by Spring Boot 4.0; Gradle 9 deferred until openapi-generator and ktlint-gradle confirm compatibility.
|
||||
|
||||
## Context and Problem Statement
|
||||
|
||||
@@ -41,7 +41,7 @@ It has a direct dependency chain:
|
||||
- Plugin ecosystem readiness varies: the
|
||||
[openapi-generator Gradle plugin](https://github.com/OpenAPITools/openapi-generator/issues/22084)
|
||||
has a known Gradle 9 issue in its generated build files, and
|
||||
[ktlint-gradle 12.1.1](https://github.com/JLLeitschuh/ktlint-gradle) Gradle 9 support
|
||||
[ktlint-gradle 14.2.0](https://github.com/JLLeitschuh/ktlint-gradle) Gradle 9 support
|
||||
needs verification.
|
||||
- The [`io.spring.dependency-management` plugin](https://github.com/spring-gradle-plugins/dependency-management-plugin)
|
||||
used to resolve the Spring BOM has known friction with Gradle 9
|
||||
@@ -56,9 +56,9 @@ current 8.8 is necessary regardless of whether Gradle 9 is chosen.
|
||||
* Gradle 8.14 (minimum for Spring Boot 4.0)
|
||||
* Gradle 9.x
|
||||
|
||||
### [Gradle 8.x](https://docs.gradle.org/8.8/release-notes.html)
|
||||
### [Gradle 8.x](https://docs.gradle.org/8.14.5/release-notes.html)
|
||||
|
||||
The currently configured series (wrapper at 8.8, to be bumped to 8.14+ for Spring Boot 4.0).
|
||||
The currently configured series (wrapper at 8.14.5, upgraded from 8.8 as part of the Spring Boot 4.0 migration).
|
||||
|
||||
#### Advantages
|
||||
|
||||
@@ -91,7 +91,7 @@ The current major release (9.5.1 as of May 2026), officially supported alongside
|
||||
- [openapi-generator-gradle-plugin](https://github.com/OpenAPITools/openapi-generator/issues/22084)
|
||||
has a known Gradle 9 issue in generated `build.gradle` files; impact on the spec-first
|
||||
code generation task specifically needs verification.
|
||||
- [ktlint-gradle 12.1.1](https://github.com/JLLeitschuh/ktlint-gradle) Gradle 9 support
|
||||
- [ktlint-gradle 14.2.0](https://github.com/JLLeitschuh/ktlint-gradle) Gradle 9 support
|
||||
needs verification.
|
||||
- Configuration Cache mandatory means any currently uncached plugin will break the build
|
||||
until updated, complicating initial setup.
|
||||
@@ -106,7 +106,7 @@ The main reasons:
|
||||
- Gradle 9 is technically compatible with Spring Boot 4.0, but the mandatory Configuration Cache
|
||||
creates unacceptable risk until the openapi-generator-gradle-plugin Gradle 9 compatibility
|
||||
is confirmed (see [issue #22084](https://github.com/OpenAPITools/openapi-generator/issues/22084)).
|
||||
- ktlint-gradle 12.1.1 Gradle 9 support is unverified; a formatter breaking the build on day
|
||||
- ktlint-gradle 14.2.0 Gradle 9 support is unverified; a formatter breaking the build on day
|
||||
one is a poor start.
|
||||
|
||||
Upgrade trigger: revisit Gradle 9 once openapi-generator and ktlint-gradle confirm support.
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
**Status:**
|
||||
- proposed: 2026-06-09
|
||||
- accepted: -
|
||||
- accepted: 2026-06-09
|
||||
- rejected: -
|
||||
- superseded: -
|
||||
|
||||
**Decision [proposed]:** Spring Boot 4.0 — only actively-supported version; springmockk workaround via @TestConfiguration until a compatible release is published.
|
||||
**Decision [accepted]:** Spring Boot 4.0.6 — only actively-supported version; springmockk 5.0.1 provides @MockkBean for Spring Boot 4.0.
|
||||
|
||||
## Context and Problem Statement
|
||||
|
||||
@@ -34,12 +34,12 @@ Spring Boot 4.0 moves to Spring Framework 7 and Jakarta EE 11.
|
||||
|
||||
The key ecosystem dependencies and their Spring Boot 4.0 compatibility status:
|
||||
|
||||
| Dependency | Role | Spring Boot 4.0 status |
|
||||
|---|---|---|
|
||||
| [picocli-spring-boot-starter](https://picocli.info/) | CLI wiring | Untested; last documented target is Spring Boot 3.1. The integration is minimal (one `IFactory` bean), so it likely works in practice. |
|
||||
| [springmockk](https://github.com/Ninja-Squad/springmockk) | `@MockkBean` in tests | **Incompatible** — confirmed Spring Boot 3.x only. |
|
||||
| [kotest-extensions-spring](https://github.com/kotest/kotest-extensions-spring) | Kotest + `@SpringBootTest` | Compatibility unconfirmed. |
|
||||
| [wiremock-spring-boot](https://wiremock.org/docs/spring-boot/) | HTTP stubbing in tests | **Compatible** — Spring Boot 4.0 explicitly supported since January 2026. |
|
||||
| Dependency | Version | Role | Spring Boot 4.0 status |
|
||||
|---|---|---|---|
|
||||
| [picocli-spring-boot-starter](https://picocli.info/) | 4.7.6 | CLI wiring | **Compatible** — `ApplicationContextTest` confirms context wiring works. |
|
||||
| [springmockk](https://github.com/Ninja-Squad/springmockk) | 5.0.1 | `@MockkBean` in tests | **Compatible** — 5.x released specifically for Spring Boot 4.0; `@MockkBean` verified. |
|
||||
| [kotest-extensions-spring](https://github.com/kotest/kotest-extensions-spring) | 6.1.5 | Kotest + `@SpringBootTest` | **Compatible** — 6.x (new groupId `io.kotest`) required; config moved to `io.kotest.provided.ProjectConfig`. |
|
||||
| [wiremock-spring-boot](https://wiremock.org/docs/spring-boot/) | 4.2.1 | HTTP stubbing in tests | **Compatible** — Spring Boot 4.0 explicitly supported since January 2026. |
|
||||
|
||||
## Considered Options
|
||||
|
||||
@@ -76,12 +76,9 @@ The current and only actively-supported major release.
|
||||
|
||||
#### Disadvantages
|
||||
|
||||
- **springmockk is incompatible.** The `@MockkBean` / `@SpykBean` annotations are not available.
|
||||
This matters in particular for `@WebMvcTest` (controller error-behaviour) and
|
||||
`@DataJpaTest`-adjacent tests where collaborators need to be mocked in the Spring context.
|
||||
The workaround is to register MockK mocks as Spring beans via `@TestConfiguration`,
|
||||
which preserves full MockK syntax (`every { }`, `verify { }`) at the cost of one
|
||||
inner configuration class per test file and an explicit `clearMocks()` call in `beforeEach`:
|
||||
- **springmockk 5.x is required.** Version 4.x is Spring Boot 3.x only. Version 5.0.1 adds
|
||||
Spring Boot 4.0 support; `@MockkBean` / `@SpykBean` work as expected.
|
||||
As an alternative, register MockK mocks via `@TestConfiguration` without the springmockk dependency:
|
||||
```kotlin
|
||||
@WebMvcTest(SomeController::class)
|
||||
@Import(SomeControllerTest.Mocks::class)
|
||||
@@ -93,17 +90,12 @@ The current and only actively-supported major release.
|
||||
@Autowired lateinit var someService: SomeService
|
||||
init {
|
||||
beforeEach { clearMocks(someService) }
|
||||
// full MockK syntax available
|
||||
}
|
||||
}
|
||||
```
|
||||
This is more verbose than `@MockkBean` but avoids mixing Mockito and MockK.
|
||||
Revisit once springmockk publishes a Spring Boot 4.0-compatible release.
|
||||
- **picocli-spring-boot-starter is untested against Spring Boot 4.0.**
|
||||
The risk is low (the auto-configuration is a single `IFactory` bean with no framework-version-specific API),
|
||||
but it cannot be assumed safe until verified by a running context test.
|
||||
- **kotest-extensions-spring** compatibility needs verification before the first
|
||||
`@SpringBootTest` test is written.
|
||||
- **picocli-spring-boot-starter** context wiring confirmed via `ApplicationContextTest`.
|
||||
- **kotest-extensions-spring 6.x** (groupId `io.kotest`) is required for Spring Boot 4.0.
|
||||
The project config must be placed at `io.kotest.provided.ProjectConfig` (Kotest 6 discovery requirement).
|
||||
- Relatively new GA; fewer community examples for the 4.0 generation.
|
||||
|
||||
## Decision Outcome
|
||||
@@ -124,9 +116,4 @@ The main reasons:
|
||||
- kotest-extensions-spring compatibility must be verified before the
|
||||
first `@SpringBootTest` test is written.
|
||||
|
||||
Open items before the code is upgraded:
|
||||
1. Bump Spring Boot to 4.0.6 and verify `ApplicationContextTest` still passes.
|
||||
2. Confirm picocli-spring-boot-starter works under the new context.
|
||||
3. Confirm kotest-extensions-spring works with Spring Boot 4.0.
|
||||
4. Replace `@MockkBean` usages with the `@TestConfiguration` + `mockk()` pattern
|
||||
until springmockk publishes a compatible release.
|
||||
All ecosystem blockers are resolved. The codebase runs Spring Boot 4.0.6.
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.5-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -17,7 +17,8 @@ class GitTallyApplication
|
||||
class CliRunner(
|
||||
private val factory: IFactory,
|
||||
private val rootCommand: GitTallyCommand,
|
||||
) : CommandLineRunner, ExitCodeGenerator {
|
||||
) : CommandLineRunner,
|
||||
ExitCodeGenerator {
|
||||
private var exitCode = 0
|
||||
|
||||
override fun run(vararg args: String) {
|
||||
|
||||
@@ -15,7 +15,5 @@ import picocli.CommandLine.Command
|
||||
description = ["Lightweight, declarative CI/CD system"],
|
||||
)
|
||||
class GitTallyCommand : Runnable {
|
||||
override fun run() {
|
||||
throw CommandLine.ParameterException(CommandLine(this), "Specify a subcommand")
|
||||
}
|
||||
override fun run(): Unit = throw CommandLine.ParameterException(CommandLine(this), "Specify a subcommand")
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
package de.hoennig.gittally
|
||||
|
||||
import io.kotest.core.config.AbstractProjectConfig
|
||||
import io.kotest.extensions.spring.SpringExtension
|
||||
|
||||
object KotestProjectConfig : AbstractProjectConfig() {
|
||||
override fun extensions() = listOf(SpringExtension)
|
||||
}
|
||||
@@ -10,7 +10,8 @@ interface Greeter {
|
||||
fun greet(name: String): String
|
||||
}
|
||||
|
||||
class MockKSmokeTest : FunSpec({
|
||||
class MockKSmokeTest :
|
||||
FunSpec({
|
||||
|
||||
test("mockk stubs and verifies") {
|
||||
val greeter = mockk<Greeter>()
|
||||
@@ -20,4 +21,4 @@ class MockKSmokeTest : FunSpec({
|
||||
|
||||
verify(exactly = 1) { greeter.greet("world") }
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -21,7 +21,6 @@ interface MessageService {
|
||||
@SpringBootTest
|
||||
@Import(SpringMockKSmokeTest.Mocks::class)
|
||||
class SpringMockKSmokeTest : FunSpec() {
|
||||
|
||||
@TestConfiguration
|
||||
class Mocks {
|
||||
@Bean
|
||||
@@ -47,7 +46,6 @@ class SpringMockKSmokeTest : FunSpec() {
|
||||
/** Verifies whether @MockkBean works with the current Spring Boot version (springmockk 4.0.2). */
|
||||
@SpringBootTest
|
||||
class MockkBeanSmokeTest : FunSpec() {
|
||||
|
||||
@MockkBean
|
||||
lateinit var messageService: MessageService
|
||||
|
||||
|
||||
@@ -5,13 +5,15 @@ import io.kotest.matchers.shouldBe
|
||||
import org.testcontainers.containers.GenericContainer
|
||||
import org.testcontainers.utility.DockerImageName
|
||||
|
||||
class TestcontainersSmokeTest : FunSpec({
|
||||
class TestcontainersSmokeTest :
|
||||
FunSpec({
|
||||
|
||||
test("Testcontainers starts a container") {
|
||||
val container = GenericContainer(DockerImageName.parse("alpine:3"))
|
||||
val container =
|
||||
GenericContainer(DockerImageName.parse("alpine:3"))
|
||||
.withCommand("sh", "-c", "sleep 30")
|
||||
container.start()
|
||||
container.isRunning shouldBe true
|
||||
container.stop()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -11,7 +11,8 @@ import java.net.http.HttpClient
|
||||
import java.net.http.HttpRequest
|
||||
import java.net.http.HttpResponse
|
||||
|
||||
class WireMockSmokeTest : FunSpec({
|
||||
class WireMockSmokeTest :
|
||||
FunSpec({
|
||||
|
||||
val server = WireMockServer(options().dynamicPort())
|
||||
|
||||
@@ -21,7 +22,8 @@ class WireMockSmokeTest : FunSpec({
|
||||
test("WireMock stubs an HTTP endpoint") {
|
||||
server.stubFor(get("/ping").willReturn(aResponse().withStatus(200).withBody("pong")))
|
||||
|
||||
val response = HttpClient.newHttpClient().send(
|
||||
val response =
|
||||
HttpClient.newHttpClient().send(
|
||||
HttpRequest.newBuilder().uri(URI.create("http://localhost:${server.port()}/ping")).build(),
|
||||
HttpResponse.BodyHandlers.ofString(),
|
||||
)
|
||||
@@ -29,4 +31,4 @@ class WireMockSmokeTest : FunSpec({
|
||||
response.statusCode() shouldBe 200
|
||||
response.body() shouldBe "pong"
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package io.kotest.provided
|
||||
|
||||
import io.kotest.core.config.AbstractProjectConfig
|
||||
import io.kotest.extensions.spring.SpringExtension
|
||||
|
||||
object ProjectConfig : AbstractProjectConfig() {
|
||||
override val extensions = listOf(SpringExtension())
|
||||
}
|
||||
Reference in New Issue
Block a user