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 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 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