diff --git a/bom/application/pom.xml b/bom/application/pom.xml
index 528a842d688bf..cea4bef22b344 100644
--- a/bom/application/pom.xml
+++ b/bom/application/pom.xml
@@ -171,10 +171,11 @@
3.27.1
0.3.1
4.21.0
- 6.2.SP1
+ 6.2.SP2-SNAPSHOT
3.5
6.4
3.4
+ 3.5.Alpha1-SNAPSHOT
5.20.0
5.8.0
2.2.1
@@ -3563,6 +3564,11 @@
quarkus-junit5-mockito-config
${project.version}
+
+ io.quarkus
+ quarkus-test-spring
+ ${project.version}
+
io.quarkus
quarkus-test-vertx
@@ -6223,6 +6229,16 @@
quarkus-spring-boot-properties-api
${quarkus-spring-boot-api.version}
+
+ io.quarkus
+ quarkus-spring-test-core-api
+ ${quarkus-spring-test-api.version}
+
+
+ io.quarkus
+ quarkus-spring-boot-test-api
+ ${quarkus-spring-test-api.version}
+
org.keycloak
diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml
index 6e1920f2f8b0a..da322d62c4475 100644
--- a/integration-tests/pom.xml
+++ b/integration-tests/pom.xml
@@ -241,6 +241,7 @@
spring-boot-properties
spring-cloud-config-client
spring-data-rest
+ spring-test
infinispan-cache-jpa
elytron-security
elytron-security-oauth2
diff --git a/integration-tests/spring-test/pom.xml b/integration-tests/spring-test/pom.xml
new file mode 100644
index 0000000000000..f14185265297f
--- /dev/null
+++ b/integration-tests/spring-test/pom.xml
@@ -0,0 +1,190 @@
+
+
+ 4.0.0
+
+
+ quarkus-integration-tests-parent
+ io.quarkus
+ 999-SNAPSHOT
+
+
+ quarkus-integration-test-spring-test
+ Quarkus - Integration Tests - Spring Web
+
+
+
+ io.quarkus
+ quarkus-rest-jackson
+
+
+ io.quarkus
+ quarkus-spring-web
+
+
+ io.quarkus
+ quarkus-undertow
+
+
+ io.quarkus
+ quarkus-spring-di
+
+
+ io.quarkus
+ quarkus-logging-json
+
+
+
+ io.quarkus
+ quarkus-junit5-mockito
+ test
+
+
+ io.quarkus
+ quarkus-test-spring
+ test
+
+
+ io.quarkus
+ quarkus-junit5
+ test
+
+
+ io.quarkus
+ quarkus-junit5-internal
+ test
+
+
+ io.rest-assured
+ rest-assured
+ test
+
+
+
+
+ io.quarkus
+ quarkus-rest-jackson-deployment
+ ${project.version}
+ pom
+ test
+
+
+ *
+ *
+
+
+
+
+ io.quarkus
+ quarkus-spring-web-deployment
+ ${project.version}
+ pom
+ test
+
+
+ *
+ *
+
+
+
+
+ io.quarkus
+ quarkus-undertow-deployment
+ ${project.version}
+ pom
+ test
+
+
+ *
+ *
+
+
+
+
+ io.quarkus
+ quarkus-rest-jackson-deployment
+ ${project.version}
+ pom
+ test
+
+
+ *
+ *
+
+
+
+
+ io.quarkus
+ quarkus-logging-json-deployment
+ ${project.version}
+ pom
+ test
+
+
+ *
+ *
+
+
+
+
+ io.quarkus
+ quarkus-spring-di-deployment
+ ${project.version}
+ pom
+ test
+
+
+ *
+ *
+
+
+
+
+
+
+
+
+
+ io.quarkus
+ quarkus-maven-plugin
+
+
+
+ build
+
+
+
+
+
+ maven-surefire-plugin
+
+
+ tpt
+ test
+
+ test
+
+
+
+ test1,whatever
+ true
+
+
+
+
+ prod-mode
+ test
+
+ test
+
+
+ **/*PMT.java
+
+
+
+
+
+
+
+
diff --git a/integration-tests/spring-test/src/main/java/io/quarkus/it/spring/web/Greeting.java b/integration-tests/spring-test/src/main/java/io/quarkus/it/spring/web/Greeting.java
new file mode 100644
index 0000000000000..0324bdf1e2031
--- /dev/null
+++ b/integration-tests/spring-test/src/main/java/io/quarkus/it/spring/web/Greeting.java
@@ -0,0 +1,14 @@
+package io.quarkus.it.spring.web;
+
+public class Greeting {
+
+ private final String message;
+
+ public Greeting(String message) {
+ this.message = message;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+}
diff --git a/integration-tests/spring-test/src/main/java/io/quarkus/it/spring/web/GreetingController.java b/integration-tests/spring-test/src/main/java/io/quarkus/it/spring/web/GreetingController.java
new file mode 100644
index 0000000000000..d0872ed632fa2
--- /dev/null
+++ b/integration-tests/spring-test/src/main/java/io/quarkus/it/spring/web/GreetingController.java
@@ -0,0 +1,34 @@
+package io.quarkus.it.spring.web;
+
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/greeting")
+public class GreetingController {
+
+ final GreetingService greetingService;
+
+ public GreetingController(GreetingService greetingService) {
+ this.greetingService = greetingService;
+ }
+
+ @GetMapping(path = "/json/{message}")
+ public Greeting greet(@PathVariable String message, @RequestParam String suffix) {
+ return greetingService.greet(message + suffix);
+ }
+
+ @GetMapping(path = "/re/json/{message}")
+ public ResponseEntity responseEntityGreeting(@PathVariable String message, @RequestParam String suffix) {
+ return ResponseEntity.ok(greetingService.greet(message + suffix));
+ }
+ //
+ // @PostMapping(path = "/person")
+ // public Greeting newGreeting(@RequestBody Person person) {
+ // return new Greeting("hello " + person.getName());
+ // }
+}
diff --git a/integration-tests/spring-test/src/main/java/io/quarkus/it/spring/web/GreetingService.java b/integration-tests/spring-test/src/main/java/io/quarkus/it/spring/web/GreetingService.java
new file mode 100644
index 0000000000000..d93fa3b6a7bbb
--- /dev/null
+++ b/integration-tests/spring-test/src/main/java/io/quarkus/it/spring/web/GreetingService.java
@@ -0,0 +1,11 @@
+package io.quarkus.it.spring.web;
+
+import org.springframework.stereotype.Service;
+
+@Service
+public class GreetingService {
+
+ public Greeting greet(String message) {
+ return new Greeting(message);
+ }
+}
diff --git a/integration-tests/spring-test/src/main/resources/application.properties b/integration-tests/spring-test/src/main/resources/application.properties
new file mode 100644
index 0000000000000..6efc0d5e85ce0
--- /dev/null
+++ b/integration-tests/spring-test/src/main/resources/application.properties
@@ -0,0 +1 @@
+junit.jupiter.extensions.autodetection.enabled=true
diff --git a/integration-tests/spring-test/src/test/java/io/quarkus/it/spring/web/SpringBootExampleTest.java b/integration-tests/spring-test/src/test/java/io/quarkus/it/spring/web/SpringBootExampleTest.java
new file mode 100644
index 0000000000000..c879b69adaa8d
--- /dev/null
+++ b/integration-tests/spring-test/src/test/java/io/quarkus/it/spring/web/SpringBootExampleTest.java
@@ -0,0 +1,24 @@
+package io.quarkus.it.spring.web;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+
+import io.quarkus.arc.Arc;
+
+@SpringBootTest(properties = { "mi.propiedad.test=valor123" })
+public class SpringBootExampleTest {
+
+ @Test
+ public void testQuarkusIsRunning() {
+ // Verifica que Quarkus está corriendo
+ assertNotNull(Arc.container(), "Quarkus container should be running");
+
+ // Verifica que la propiedad de @SpringBootTest se aplicó
+ String value = System.getProperty("mi.propiedad.test");
+ assertEquals("valor123", value, "Property from @SpringBootTest should be set");
+
+ System.out.println("✅ Test ejecutado correctamente con Quarkus!");
+ }
+}
diff --git a/integration-tests/spring-test/src/test/java/io/quarkus/it/spring/web/SpringControllerIT.java b/integration-tests/spring-test/src/test/java/io/quarkus/it/spring/web/SpringControllerIT.java
new file mode 100644
index 0000000000000..c0e2aaa731cf4
--- /dev/null
+++ b/integration-tests/spring-test/src/test/java/io/quarkus/it/spring/web/SpringControllerIT.java
@@ -0,0 +1,7 @@
+package io.quarkus.it.spring.web;
+
+import io.quarkus.test.junit.QuarkusIntegrationTest;
+
+@QuarkusIntegrationTest
+public class SpringControllerIT extends SpringControllerTest {
+}
diff --git a/integration-tests/spring-test/src/test/java/io/quarkus/it/spring/web/SpringControllerTest.java b/integration-tests/spring-test/src/test/java/io/quarkus/it/spring/web/SpringControllerTest.java
new file mode 100644
index 0000000000000..973667bf3db86
--- /dev/null
+++ b/integration-tests/spring-test/src/test/java/io/quarkus/it/spring/web/SpringControllerTest.java
@@ -0,0 +1,61 @@
+package io.quarkus.it.spring.web;
+
+import static org.hamcrest.Matchers.containsString;
+
+import org.junit.jupiter.api.Test;
+
+import io.quarkus.test.junit.QuarkusTest;
+import io.restassured.RestAssured;
+
+@QuarkusTest
+public class SpringControllerTest {
+
+ @Test
+ public void testJsonResult() {
+ RestAssured.when().get("/greeting/json/hello").then()
+ .contentType("application/json")
+ .body(containsString("hello"));
+ }
+
+ @Test
+ public void testJsonResultFromResponseEntity() {
+ RestAssured.when().get("/greeting/re/json/hello").then()
+ .contentType("application/json")
+ .body(containsString("hello"));
+ }
+
+ @Test
+ public void testJsonResult2() {
+ RestAssured.when().get("/greeting/json/hello?suffix=000").then()
+ .contentType("application/json")
+ .body(containsString("hello000"));
+ }
+
+ @Test
+ public void testInvalidJsonInputAndResult() {
+ RestAssured.given().contentType("application/json").body("{\"name\":\"\"}").post("/greeting/person").then()
+ .statusCode(400);
+ }
+
+ @Test
+ public void testJsonInputAndResult() {
+ RestAssured.given().contentType("application/json").body("{\"name\":\"George\"}").post("/greeting/person").then()
+ .contentType("application/json")
+ .body(containsString("hello George"));
+ }
+
+ @Test
+ public void testRestControllerWithoutRequestMapping() {
+ RestAssured.when().get("/hello").then()
+ .body(containsString("hello"));
+ }
+
+ @Test
+ public void testMethodReturningXmlContent() {
+ RestAssured.when().get("/book")
+ .then()
+ .statusCode(200)
+ .contentType("application/xml")
+ .body(containsString("steel"));
+ }
+}
diff --git a/integration-tests/spring-test/src/test/resources/application.properties b/integration-tests/spring-test/src/test/resources/application.properties
new file mode 100644
index 0000000000000..6efc0d5e85ce0
--- /dev/null
+++ b/integration-tests/spring-test/src/test/resources/application.properties
@@ -0,0 +1 @@
+junit.jupiter.extensions.autodetection.enabled=true
diff --git a/test-framework/junit5-mockito/src/main/java/io/quarkus/test/junit/mockito/internal/ResetMockitoMocksAfterEachCallback.java b/test-framework/junit5-mockito/src/main/java/io/quarkus/test/junit/mockito/internal/ResetMockitoMocksAfterEachCallback.java
index 7a7a1adf95d0d..def42cea40e7d 100644
--- a/test-framework/junit5-mockito/src/main/java/io/quarkus/test/junit/mockito/internal/ResetMockitoMocksAfterEachCallback.java
+++ b/test-framework/junit5-mockito/src/main/java/io/quarkus/test/junit/mockito/internal/ResetMockitoMocksAfterEachCallback.java
@@ -7,6 +7,7 @@ public class ResetMockitoMocksAfterEachCallback implements QuarkusTestAfterEachC
@Override
public void afterEach(QuarkusTestMethodContext context) {
+ System.out.println("AfterEach ejecutando: ResetMockitoMocksAfterEachCallback");
MockitoMocksTracker.reset(context.getTestInstance());
}
}
diff --git a/test-framework/pom.xml b/test-framework/pom.xml
index 567cff9cfd3a6..c6e70e24a6232 100644
--- a/test-framework/pom.xml
+++ b/test-framework/pom.xml
@@ -8,6 +8,12 @@
999-SNAPSHOT
../build-parent/pom.xml
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+
+
4.0.0
quarkus-test-framework
@@ -43,6 +49,7 @@
kafka-companion
google-cloud-functions
observability
+ spring-test
diff --git a/test-framework/spring-test/pom.xml b/test-framework/spring-test/pom.xml
new file mode 100644
index 0000000000000..fdd7760ac472e
--- /dev/null
+++ b/test-framework/spring-test/pom.xml
@@ -0,0 +1,64 @@
+
+
+ 4.0.0
+
+
+ io.quarkus
+ quarkus-test-framework
+ 999-SNAPSHOT
+
+
+ quarkus-test-spring
+ Quarkus - Test Framework - Spring
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+
+
+ io.smallrye.config
+ smallrye-config
+
+
+ org.junit.jupiter
+ junit-jupiter
+
+
+
+ io.quarkus
+ quarkus-core
+
+
+ io.quarkus
+ quarkus-junit5
+
+
+ io.quarkus
+ quarkus-spring-boot-test-api
+
+
+ io.quarkus
+ quarkus-spring-test-core-api
+
+
+ io.quarkus
+ quarkus-spring-test-core-api
+
+
+ io.quarkus
+ quarkus-spring-core-api
+
+
+ io.quarkus
+ quarkus-spring-beans-api
+
+
+ io.quarkus
+ quarkus-core-deployment
+
+
+
+
diff --git a/test-framework/spring-test/src/main/java/io/quarkus/test/spring/SpringBootTestAutoExtension.java b/test-framework/spring-test/src/main/java/io/quarkus/test/spring/SpringBootTestAutoExtension.java
new file mode 100644
index 0000000000000..b50a61baa5217
--- /dev/null
+++ b/test-framework/spring-test/src/main/java/io/quarkus/test/spring/SpringBootTestAutoExtension.java
@@ -0,0 +1,84 @@
+package io.quarkus.test.spring;
+
+import java.lang.annotation.Annotation;
+
+import org.junit.jupiter.api.extension.ExtensionContext;
+
+import io.quarkus.test.junit.QuarkusTestExtension;
+
+public class SpringBootTestAutoExtension extends QuarkusTestExtension {
+
+ private static final String SPRING_BOOT_TEST_ANNOTATION = "org.springframework.boot.test.context.SpringBootTest";
+
+ @Override
+ public void beforeAll(ExtensionContext context) throws Exception {
+ System.out.println("========================================");
+ System.out.println("SpringBootTestAutoExtension LOADED!");
+ System.out.println("========================================");
+
+ Class> testClass = context.getRequiredTestClass();
+
+ // Detecta si tiene @SpringBootTest (la de Spring original)
+ boolean hasSpringBootTest = hasSpringBootTestAnnotation(testClass);
+
+ if (hasSpringBootTest) {
+ System.out.println("✅ Spring Boot Test Annotation detected");
+
+ // Procesa la configuración de @SpringBootTest
+ processSpringBootTestConfiguration(testClass);
+
+ // CRÍTICO: Llama a super.beforeAll() para activar Quarkus
+ System.out.println("🚀 Activando Quarkus...");
+ super.beforeAll(context);
+ System.out.println("✅ Quarkus activado!");
+
+ } else {
+ System.out.println("ℹ️ No @SpringBootTest detected, skipping");
+ // Si no tiene @SpringBootTest, no hacemos nada
+ // Esto permite que otros tests normales funcionen sin interferencia
+ }
+ }
+
+ private boolean hasSpringBootTestAnnotation(Class> testClass) {
+ try {
+ Class extends Annotation> springBootTestClass = Class.forName(SPRING_BOOT_TEST_ANNOTATION)
+ .asSubclass(Annotation.class);
+
+ return testClass.isAnnotationPresent(springBootTestClass);
+
+ } catch (ClassNotFoundException e) {
+ // La anotación de Spring no está en el classpath
+ return false;
+ }
+ }
+
+ private void processSpringBootTestConfiguration(Class> testClass) {
+ try {
+ Class extends Annotation> springBootTestClass = Class.forName(SPRING_BOOT_TEST_ANNOTATION)
+ .asSubclass(Annotation.class);
+
+ Annotation annotation = testClass.getAnnotation(springBootTestClass);
+
+ if (annotation != null) {
+ // Usa reflection para leer el atributo 'properties'
+ java.lang.reflect.Method propertiesMethod = springBootTestClass.getMethod("properties");
+ String[] properties = (String[]) propertiesMethod.invoke(annotation);
+
+ System.out.println("📝 Procesando properties de @SpringBootTest:");
+ for (String property : properties) {
+ System.out.println(" - " + property);
+
+ // Aplica como system property
+ String[] parts = property.split("=", 2);
+ if (parts.length == 2) {
+ System.setProperty(parts[0].trim(), parts[1].trim());
+ }
+ }
+ }
+
+ } catch (Exception e) {
+ System.err.println("⚠️ Error procesando @SpringBootTest: " + e.getMessage());
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/test-framework/spring-test/src/main/resources/META-INF/services/org.junit.jupiter.api.extension.Extension b/test-framework/spring-test/src/main/resources/META-INF/services/org.junit.jupiter.api.extension.Extension
new file mode 100644
index 0000000000000..3ec17b1adaff2
--- /dev/null
+++ b/test-framework/spring-test/src/main/resources/META-INF/services/org.junit.jupiter.api.extension.Extension
@@ -0,0 +1 @@
+io.quarkus.test.spring.SpringBootTestAutoExtension
diff --git a/test-framework/spring-test/src/main/resources/junit-platform.properties b/test-framework/spring-test/src/main/resources/junit-platform.properties
new file mode 100644
index 0000000000000..079f61d0ea575
--- /dev/null
+++ b/test-framework/spring-test/src/main/resources/junit-platform.properties
@@ -0,0 +1,2 @@
+junit.jupiter.extensions.autodetection.enabled=true
+#junit.jupiter.testclass.order.default=io.quarkus.test.config.QuarkusClassOrderer