diff --git a/core/runtime/src/main/java/io/quarkus/runtime/test/TestHttpEndpointProvider.java b/core/runtime/src/main/java/io/quarkus/runtime/test/TestHttpEndpointProvider.java new file mode 100644 index 0000000000000..e6c1bd6d07be9 --- /dev/null +++ b/core/runtime/src/main/java/io/quarkus/runtime/test/TestHttpEndpointProvider.java @@ -0,0 +1,24 @@ +package io.quarkus.runtime.test; + +import java.util.ArrayList; +import java.util.List; +import java.util.ServiceLoader; +import java.util.function.Function; + +/** + * Interface that can be used to integrate with the TestHTTPEndpoint infrastructure + */ +public interface TestHttpEndpointProvider { + + Function, String> endpointProvider(); + + static List, String>> load() { + List, String>> ret = new ArrayList<>(); + for (TestHttpEndpointProvider i : ServiceLoader.load(TestHttpEndpointProvider.class, + Thread.currentThread().getContextClassLoader())) { + ret.add(i.endpointProvider()); + } + return ret; + } + +} diff --git a/docs/src/main/asciidoc/getting-started-testing.adoc b/docs/src/main/asciidoc/getting-started-testing.adoc index c24a5115e240a..34417bc23280d 100644 --- a/docs/src/main/asciidoc/getting-started-testing.adoc +++ b/docs/src/main/asciidoc/getting-started-testing.adoc @@ -228,6 +228,118 @@ public class StaticContentTest { For now `@TestHTTPResource` allows you to inject `URI`, `URL` and `String` representations of the URL. +== Testing a specific endpoint + +Both RESTassured and `@TestHTTPResource` allow you to specify the endpoint class you are testing rather than hard coding +a path. This currently supports both JAX-RS endpoints, Servlets and Reactive Routes. This makes it a lot easier to see exactly which endpoints +a given test is testing. + +For the purposes of these examples I am going to assume we have an endpoint that looks like the following: + +[source,java] +---- +@Path("/hello") +public class GreetingResource { + + @GET + @Produces(MediaType.TEXT_PLAIN) + public String hello() { + return "hello"; + } +} +---- + +NOTE: This currently does not support the `@ApplicationPath()` annotation to set the JAX-RS context path. Use the +`quarkus.resteasy.path` config value instead if you want a custom context path. + +=== TestHTTPResource + +You can the use the `io.quarkus.test.common.http.TestHTTPEndpoint` annotation to specify the endpoint path, and the path +will be extracted from the provided endpoint. If you also specify a value for the `TestHTTPResource` endpoint it will +be appended to the end of the endpoint path. + +[source,java] +---- +package org.acme.getting.started.testing; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.charset.StandardCharsets; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import io.quarkus.test.common.http.TestHTTPEndpoint; +import io.quarkus.test.common.http.TestHTTPResource; +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +public class StaticContentTest { + + @TestHTTPEndpoint(GreetingResource.class) // <1> + @TestHTTPResource + URL url; + + @Test + public void testIndexHtml() throws Exception { + try (InputStream in = url.openStream()) { + String contents = readStream(in); + Assertions.assertTrue(contents.equals("hello")); + } + } + + private static String readStream(InputStream in) throws IOException { + byte[] data = new byte[1024]; + int r; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + while ((r = in.read(data)) > 0) { + out.write(data, 0, r); + } + return new String(out.toByteArray(), StandardCharsets.UTF_8); + } +} +---- +<1> Because `GreetingResource` is annotated with `@Path("/hello")` the injected URL +will end with `/hello`. + +=== RESTassured + +To control the RESTassured base path (i.e. the default path that serves as the root for every +request) you can use the `io.quarkus.test.common.http.TestHTTPEndpoint` annotation. This can +be applied at the class or method level. To test out greeting resource we would do: + +[source,java] +---- +package org.acme.getting.started.testing; + +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.common.http.TestHTTPEndpoint; +import org.junit.jupiter.api.Test; + +import java.util.UUID; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.CoreMatchers.is; + +@QuarkusTest +@TestHTTPEndpoint(GreetingResource.class) //<1> +public class GreetingResourceTest { + + @Test + public void testHelloEndpoint() { + given() + .when().get() //<2> + .then() + .statusCode(200) + .body(is("hello")); + } +} +---- +<1> This tells RESTAssured to prefix all requests with `/hello`. +<2> Note we don't need to specify a path here, as `/hello` is the default for this test + == Injection into tests So far we have only covered integration style tests that test the app via HTTP endpoints, but what if we want to do unit diff --git a/extensions/resteasy-server-common/runtime/src/main/java/io/quarkus/resteasy/server/common/runtime/RESTEasyTestHttpProvider.java b/extensions/resteasy-server-common/runtime/src/main/java/io/quarkus/resteasy/server/common/runtime/RESTEasyTestHttpProvider.java new file mode 100644 index 0000000000000..a0b405e4a721b --- /dev/null +++ b/extensions/resteasy-server-common/runtime/src/main/java/io/quarkus/resteasy/server/common/runtime/RESTEasyTestHttpProvider.java @@ -0,0 +1,70 @@ +package io.quarkus.resteasy.server.common.runtime; + +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.util.Optional; +import java.util.function.Function; + +import javax.ws.rs.Path; + +import org.eclipse.microprofile.config.ConfigProvider; + +import io.quarkus.runtime.test.TestHttpEndpointProvider; + +public class RESTEasyTestHttpProvider implements TestHttpEndpointProvider { + @Override + public Function, String> endpointProvider() { + return new Function, String>() { + @Override + public String apply(Class aClass) { + String value = getPath(aClass); + if (value == null) { + return null; + } + if (value.startsWith("/")) { + value = value.substring(1); + } + //TODO: there is not really any way to handle @ApplicationPath, we could do something for @QuarkusTest apps but we can't for + //native apps, so we just have to document the limitation + String path = "/"; + Optional appPath = ConfigProvider.getConfig().getOptionalValue("quarkus.resteasy.path", String.class); + if (appPath.isPresent()) { + path = appPath.get(); + } + if (!path.endsWith("/")) { + path = path + "/"; + } + value = path + value; + return value; + } + }; + } + + private String getPath(Class aClass) { + String value = null; + for (Annotation annotation : aClass.getAnnotations()) { + if (annotation.annotationType().getName().equals(Path.class.getName())) { + try { + value = (String) annotation.annotationType().getMethod("value").invoke(annotation); + break; + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + } + if (value == null) { + for (Class i : aClass.getInterfaces()) { + value = getPath(i); + if (value != null) { + break; + } + } + } + if (value == null) { + if (aClass.getSuperclass() != Object.class) { + value = getPath(aClass.getSuperclass()); + } + } + return value; + } +} diff --git a/extensions/resteasy-server-common/runtime/src/main/resources/META-INF/services/io.quarkus.runtime.test.TestHttpEndpointProvider b/extensions/resteasy-server-common/runtime/src/main/resources/META-INF/services/io.quarkus.runtime.test.TestHttpEndpointProvider new file mode 100644 index 0000000000000..07469473a2cd0 --- /dev/null +++ b/extensions/resteasy-server-common/runtime/src/main/resources/META-INF/services/io.quarkus.runtime.test.TestHttpEndpointProvider @@ -0,0 +1 @@ +io.quarkus.resteasy.server.common.runtime.RESTEasyTestHttpProvider \ No newline at end of file diff --git a/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/UndertowTestHttpProvider.java b/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/UndertowTestHttpProvider.java new file mode 100644 index 0000000000000..5fa77ba9d043e --- /dev/null +++ b/extensions/undertow/runtime/src/main/java/io/quarkus/undertow/runtime/UndertowTestHttpProvider.java @@ -0,0 +1,57 @@ +package io.quarkus.undertow.runtime; + +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.util.Optional; +import java.util.function.Function; + +import javax.servlet.annotation.WebServlet; + +import org.eclipse.microprofile.config.ConfigProvider; + +import io.quarkus.runtime.test.TestHttpEndpointProvider; + +public class UndertowTestHttpProvider implements TestHttpEndpointProvider { + @Override + public Function, String> endpointProvider() { + return new Function, String>() { + @Override + public String apply(Class aClass) { + String value = null; + for (Annotation annotation : aClass.getAnnotations()) { + if (annotation.annotationType().getName().equals(WebServlet.class.getName())) { + try { + String[] patterns = (String[]) annotation.annotationType().getMethod("urlPatterns") + .invoke(annotation); + if (patterns.length > 0) { + value = patterns[0]; + } + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + } + if (value == null) { + return null; + } + if (value.endsWith("/*")) { + value = value.substring(0, value.length() - 1); + } + if (value.startsWith("/")) { + value = value.substring(1); + } + String path = "/"; + Optional appPath = ConfigProvider.getConfig().getOptionalValue("quarkus.servlet.context-path", + String.class); + if (appPath.isPresent()) { + path = appPath.get(); + } + if (!path.endsWith("/")) { + path = path + "/"; + } + value = path + value; + return value; + } + }; + } +} diff --git a/extensions/undertow/runtime/src/main/resources/META-INF/services/io.quarkus.runtime.test.TestHttpEndpointProvider b/extensions/undertow/runtime/src/main/resources/META-INF/services/io.quarkus.runtime.test.TestHttpEndpointProvider new file mode 100644 index 0000000000000..f1abc59e8c339 --- /dev/null +++ b/extensions/undertow/runtime/src/main/resources/META-INF/services/io.quarkus.runtime.test.TestHttpEndpointProvider @@ -0,0 +1 @@ +io.quarkus.undertow.runtime.UndertowTestHttpProvider \ No newline at end of file diff --git a/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/ReactiveRoutesTestHttpProvider.java b/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/ReactiveRoutesTestHttpProvider.java new file mode 100644 index 0000000000000..c310a7b1d8989 --- /dev/null +++ b/extensions/vertx-web/runtime/src/main/java/io/quarkus/vertx/web/runtime/ReactiveRoutesTestHttpProvider.java @@ -0,0 +1,36 @@ +package io.quarkus.vertx.web.runtime; + +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.util.function.Function; + +import io.quarkus.runtime.test.TestHttpEndpointProvider; +import io.quarkus.vertx.web.RouteBase; + +public class ReactiveRoutesTestHttpProvider implements TestHttpEndpointProvider { + @Override + public Function, String> endpointProvider() { + return new Function, String>() { + @Override + public String apply(Class aClass) { + String value = null; + for (Annotation annotation : aClass.getAnnotations()) { + if (annotation.annotationType().getName().equals(RouteBase.class.getName())) { + try { + value = (String) annotation.annotationType().getMethod("path").invoke(annotation); + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + } + if ((value == null) || value.isEmpty()) { + return null; + } + if (!value.startsWith("/")) { + value = "/" + value; + } + return value; + } + }; + } +} diff --git a/extensions/vertx-web/runtime/src/main/resources/META-INF/services/io.quarkus.runtime.test.TestHttpEndpointProvider b/extensions/vertx-web/runtime/src/main/resources/META-INF/services/io.quarkus.runtime.test.TestHttpEndpointProvider new file mode 100644 index 0000000000000..49cbdc788af6a --- /dev/null +++ b/extensions/vertx-web/runtime/src/main/resources/META-INF/services/io.quarkus.runtime.test.TestHttpEndpointProvider @@ -0,0 +1 @@ +io.quarkus.vertx.web.runtime.ReactiveRoutesTestHttpProvider diff --git a/integration-tests/main/src/main/java/io/quarkus/it/faulttolerance/TestResource.java b/integration-tests/main/src/main/java/io/quarkus/it/faulttolerance/FaultToleranceTestResource.java similarity index 90% rename from integration-tests/main/src/main/java/io/quarkus/it/faulttolerance/TestResource.java rename to integration-tests/main/src/main/java/io/quarkus/it/faulttolerance/FaultToleranceTestResource.java index 3a7b3ce9a1c00..08a71fbf6afad 100644 --- a/integration-tests/main/src/main/java/io/quarkus/it/faulttolerance/TestResource.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/faulttolerance/FaultToleranceTestResource.java @@ -7,7 +7,7 @@ import javax.ws.rs.Path; @Path("/ft") -public class TestResource { +public class FaultToleranceTestResource { @Inject Service service; diff --git a/integration-tests/main/src/main/java/io/quarkus/it/transaction/TransactionResource.java b/integration-tests/main/src/main/java/io/quarkus/it/transaction/TransactionResource.java index 90d0ed6799798..4e8ab58923512 100644 --- a/integration-tests/main/src/main/java/io/quarkus/it/transaction/TransactionResource.java +++ b/integration-tests/main/src/main/java/io/quarkus/it/transaction/TransactionResource.java @@ -9,7 +9,7 @@ import javax.ws.rs.GET; import javax.ws.rs.Path; -@Path("/txn") +@Path("/txn/txendpoint") public class TransactionResource { @Inject diff --git a/integration-tests/main/src/test/java/io/quarkus/it/main/FaultToleranceTestCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/FaultToleranceTestCase.java index d165fad1ba269..b877b58655182 100644 --- a/integration-tests/main/src/test/java/io/quarkus/it/main/FaultToleranceTestCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/FaultToleranceTestCase.java @@ -9,13 +9,16 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import io.quarkus.it.faulttolerance.FaultToleranceTestResource; +import io.quarkus.test.common.http.TestHTTPEndpoint; import io.quarkus.test.common.http.TestHTTPResource; import io.quarkus.test.junit.QuarkusTest; @QuarkusTest public class FaultToleranceTestCase { - @TestHTTPResource("ft") + @TestHTTPEndpoint(FaultToleranceTestResource.class) + @TestHTTPResource URL uri; @Test diff --git a/integration-tests/main/src/test/java/io/quarkus/it/main/JaxRSTestCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/JaxRSTestCase.java index ce42fd7f2bc0f..5c8c5ed3036c9 100644 --- a/integration-tests/main/src/test/java/io/quarkus/it/main/JaxRSTestCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/JaxRSTestCase.java @@ -15,6 +15,7 @@ import org.junit.jupiter.api.Test; import io.quarkus.it.rest.TestResource; +import io.quarkus.test.common.http.TestHTTPEndpoint; import io.quarkus.test.junit.QuarkusTest; import io.restassured.RestAssured; import io.restassured.parsing.Parser; @@ -28,8 +29,9 @@ public void testJAXRS() { } @Test + @TestHTTPEndpoint(TestResource.class) public void testInteger() { - RestAssured.when().get("/test/int/10").then().body(is("11")); + RestAssured.when().get("/int/10").then().body(is("11")); } @Test diff --git a/integration-tests/main/src/test/java/io/quarkus/it/main/ServletTestCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/ServletTestCase.java index ecc3f14132174..5177bc37ec47c 100644 --- a/integration-tests/main/src/test/java/io/quarkus/it/main/ServletTestCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/ServletTestCase.java @@ -5,6 +5,8 @@ import org.junit.jupiter.api.Test; +import io.quarkus.it.web.TestServlet; +import io.quarkus.test.common.http.TestHTTPEndpoint; import io.quarkus.test.junit.QuarkusTest; import io.restassured.RestAssured; @@ -12,8 +14,9 @@ public class ServletTestCase { @Test + @TestHTTPEndpoint(TestServlet.class) public void testServlet() { - RestAssured.when().get("/testservlet").then() + RestAssured.when().get().then() .body(is("A message")); } diff --git a/integration-tests/main/src/test/java/io/quarkus/it/main/TransactionTestCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/TransactionTestCase.java index f229df74474ad..1be1b8a40d92f 100644 --- a/integration-tests/main/src/test/java/io/quarkus/it/main/TransactionTestCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/TransactionTestCase.java @@ -4,15 +4,18 @@ import org.junit.jupiter.api.Test; +import io.quarkus.it.transaction.TransactionResource; +import io.quarkus.test.common.http.TestHTTPEndpoint; import io.quarkus.test.junit.QuarkusTest; import io.restassured.RestAssured; @QuarkusTest +@TestHTTPEndpoint(TransactionResource.class) public class TransactionTestCase { @Test public void testTransaction() { - RestAssured.when().get("/txn").then() + RestAssured.when().get().then() .body(is("true")); } diff --git a/integration-tests/vertx-web/src/test/java/io/quarkus/it/vertx/SimpleEndpointTestCase.java b/integration-tests/vertx-web/src/test/java/io/quarkus/it/vertx/SimpleEndpointTestCase.java index a678aa300cc84..796b255c4eb60 100644 --- a/integration-tests/vertx-web/src/test/java/io/quarkus/it/vertx/SimpleEndpointTestCase.java +++ b/integration-tests/vertx-web/src/test/java/io/quarkus/it/vertx/SimpleEndpointTestCase.java @@ -6,23 +6,25 @@ import org.junit.jupiter.api.Test; +import io.quarkus.test.common.http.TestHTTPEndpoint; import io.quarkus.test.junit.QuarkusTest; @QuarkusTest +@TestHTTPEndpoint(SimpleEndpoint.class) public class SimpleEndpointTestCase { @Test public void testEndpoint() throws Exception { - when().get("/simple/person").then().statusCode(200) + when().get("/person").then().statusCode(200) .body("name", is("Jan")) .header("content-type", "application/json"); - when().get("/simple/pet").then().statusCode(200) + when().get("/pet").then().statusCode(200) .body("name", is("Jack")) .header("content-type", "application/json"); - when().get("/simple/pong").then().statusCode(200) + when().get("/pong").then().statusCode(200) .body("name", is("ping")) .header("content-type", "application/json"); - given().body("{\"name\":\"pi\"}").post("/simple/data").then().statusCode(200) + given().body("{\"name\":\"pi\"}").post("/data").then().statusCode(200) .body("name", is("pipi")) .header("content-type", "application/json"); } diff --git a/test-framework/common/src/main/java/io/quarkus/test/common/RestAssuredURLManager.java b/test-framework/common/src/main/java/io/quarkus/test/common/RestAssuredURLManager.java index 76baef2066f38..6a393ffbf90f4 100644 --- a/test-framework/common/src/main/java/io/quarkus/test/common/RestAssuredURLManager.java +++ b/test-framework/common/src/main/java/io/quarkus/test/common/RestAssuredURLManager.java @@ -55,10 +55,18 @@ private static int getPortFromConfig(String key, int defaultValue) { } public static void setURL(boolean useSecureConnection) { - setURL(useSecureConnection, null); + setURL(useSecureConnection, null, null); + } + + public static void setURL(boolean useSecureConnection, String additionalPath) { + setURL(useSecureConnection, null, additionalPath); } public static void setURL(boolean useSecureConnection, Integer port) { + setURL(useSecureConnection, port, null); + } + + public static void setURL(boolean useSecureConnection, Integer port, String additionalPath) { if (portField != null) { try { oldPort = (Integer) portField.get(null); @@ -91,8 +99,28 @@ public static void setURL(boolean useSecureConnection, Integer port) { oldBasePath = (String) basePathField.get(null); Optional basePath = ConfigProvider.getConfig().getOptionalValue("quarkus.http.root-path", String.class); - if (basePath.isPresent()) { - basePathField.set(null, basePath.get()); + if (basePath.isPresent() || additionalPath != null) { + StringBuilder bp = new StringBuilder(); + if (basePath.isPresent()) { + if (basePath.get().startsWith("/")) { + bp.append(basePath.get().substring(1)); + } else { + bp.append(basePath.get()); + } + if (bp.toString().endsWith("/")) { + bp.setLength(bp.length() - 1); + } + } + if (additionalPath != null) { + if (!additionalPath.startsWith("/")) { + bp.append("/"); + } + bp.append(additionalPath); + if (bp.toString().endsWith("/")) { + bp.setLength(bp.length() - 1); + } + } + basePathField.set(null, bp.toString()); } } catch (IllegalAccessException e) { e.printStackTrace(); diff --git a/test-framework/common/src/main/java/io/quarkus/test/common/http/TestHTTPEndpoint.java b/test-framework/common/src/main/java/io/quarkus/test/common/http/TestHTTPEndpoint.java new file mode 100644 index 0000000000000..de9d062df30a8 --- /dev/null +++ b/test-framework/common/src/main/java/io/quarkus/test/common/http/TestHTTPEndpoint.java @@ -0,0 +1,28 @@ +package io.quarkus.test.common.http; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicates that this test class or method is testing a specific endpoint. + * + * RESTAssured will also have its base URL modified + * so all URLS can be given relative to the root of the the provided resource class. It + * can also be applied to {@link TestHTTPResource} fields to set the base path. + * + * + * This mechanism is pluggable, and currently supports JAX-RS endpoints, Servlets and Reactive Routes. + * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.METHOD, ElementType.TYPE, ElementType.FIELD }) +public @interface TestHTTPEndpoint { + + /** + * The HTTP endpoint that is under test. All injected URL's will point to the + * root path of the provided resource. + */ + Class value(); +} diff --git a/test-framework/common/src/main/java/io/quarkus/test/common/http/TestHTTPResourceManager.java b/test-framework/common/src/main/java/io/quarkus/test/common/http/TestHTTPResourceManager.java index 7d30792169b8c..4cf897443d072 100644 --- a/test-framework/common/src/main/java/io/quarkus/test/common/http/TestHTTPResourceManager.java +++ b/test-framework/common/src/main/java/io/quarkus/test/common/http/TestHTTPResourceManager.java @@ -4,13 +4,16 @@ import java.net.URI; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.ServiceLoader; +import java.util.function.Function; import org.eclipse.microprofile.config.Config; import org.eclipse.microprofile.config.ConfigProvider; import io.quarkus.bootstrap.app.RunningQuarkusApplication; +import io.quarkus.runtime.test.TestHttpEndpointProvider; public class TestHTTPResourceManager { @@ -45,6 +48,10 @@ public static String getSslUri(RunningQuarkusApplication application) { } public static void inject(Object testCase) { + inject(testCase, TestHttpEndpointProvider.load()); + } + + public static void inject(Object testCase, List, String>> endpointProviders) { Map, TestHTTPResourceProvider> providers = getProviders(); Class c = testCase.getClass(); while (c != Object.class) { @@ -57,6 +64,30 @@ public static void inject(Object testCase) { "Unable to inject TestHTTPResource field " + f + " as no provider exists for the type"); } String path = resource.value(); + String endpointPath = null; + TestHTTPEndpoint endpointAnnotation = f.getAnnotation(TestHTTPEndpoint.class); + if (endpointAnnotation != null) { + for (Function, String> func : endpointProviders) { + endpointPath = func.apply(endpointAnnotation.value()); + if (endpointPath != null) { + break; + } + } + if (endpointPath == null) { + throw new RuntimeException( + "Could not determine the endpoint path for " + endpointAnnotation.value() + " to inject " + + f); + } + } + if (!path.isEmpty() && endpointPath != null) { + if (!endpointPath.endsWith("/")) { + path = endpointPath + "/" + path; + } else { + path = endpointPath + path; + } + } else if (endpointPath != null) { + path = endpointPath; + } String val; if (resource.ssl()) { if (path.startsWith("/")) { diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/NativeTestExtension.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/NativeTestExtension.java index 63b8f63b255dc..c6d9c6f59a33e 100644 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/NativeTestExtension.java +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/NativeTestExtension.java @@ -3,7 +3,9 @@ import java.io.Closeable; import java.io.IOException; import java.lang.reflect.Field; +import java.util.List; import java.util.Map; +import java.util.function.Function; import javax.inject.Inject; @@ -14,6 +16,7 @@ import org.junit.jupiter.api.extension.TestInstancePostProcessor; import org.junit.platform.commons.JUnitException; +import io.quarkus.runtime.test.TestHttpEndpointProvider; import io.quarkus.test.common.NativeImageLauncher; import io.quarkus.test.common.PropertyTestUtil; import io.quarkus.test.common.RestAssuredURLManager; @@ -26,6 +29,8 @@ public class NativeTestExtension private static boolean failedBoot; + private static List, String>> testHttpEndpointProviders; + @Override public void afterEach(ExtensionContext context) throws Exception { if (!failedBoot) { @@ -37,7 +42,7 @@ public void afterEach(ExtensionContext context) throws Exception { @Override public void beforeEach(ExtensionContext context) throws Exception { if (!failedBoot) { - RestAssuredURLManager.setURL(false); + RestAssuredURLManager.setURL(false, QuarkusTestExtension.getEndpointPath(context, testHttpEndpointProviders)); TestScopeManager.setup(true); } } @@ -68,6 +73,8 @@ public void beforeAll(ExtensionContext extensionContext) throws Exception { } state = new ExtensionState(testResourceManager, launcher, true); store.put(ExtensionState.class.getName(), state); + + testHttpEndpointProviders = TestHttpEndpointProvider.load(); } catch (Exception e) { failedBoot = true; diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java index 8d1c2a2110fd0..36591a668f6c2 100644 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/QuarkusTestExtension.java @@ -64,12 +64,14 @@ import io.quarkus.deployment.builditem.TestClassBeanBuildItem; import io.quarkus.deployment.builditem.TestClassPredicateBuildItem; import io.quarkus.runtime.configuration.ProfileManager; +import io.quarkus.runtime.test.TestHttpEndpointProvider; import io.quarkus.test.common.PathTestHelper; import io.quarkus.test.common.PropertyTestUtil; import io.quarkus.test.common.RestAssuredURLManager; import io.quarkus.test.common.TestClassIndexer; import io.quarkus.test.common.TestResourceManager; import io.quarkus.test.common.TestScopeManager; +import io.quarkus.test.common.http.TestHTTPEndpoint; import io.quarkus.test.common.http.TestHTTPResourceManager; import io.quarkus.test.junit.buildchain.TestBuildChainCustomizerProducer; import io.quarkus.test.junit.callback.QuarkusTestAfterEachCallback; @@ -103,6 +105,7 @@ public class QuarkusTestExtension private static List afterEachCallbacks; private static Class quarkusTestMethodContextClass; private static Class quarkusTestProfile; + private static List, String>> testHttpEndpointProviders; private static DeepClone deepClone; @@ -191,7 +194,9 @@ private ExtensionState doJavaStart(ExtensionContext context, Class props = new HashMap<>(); props.put(TEST_LOCATION, testClassLocation); props.put(TEST_CLASS, requiredTestClass); - AugmentAction augmentAction = curatedApplication.createAugmentor(TestBuildChainFunction.class.getName(), props); + AugmentAction augmentAction = curatedApplication + .createAugmentor(TestBuildChainFunction.class.getName(), props); + testHttpEndpointProviders = TestHttpEndpointProvider.load(); StartupAction startupAction = augmentAction.createInitialRuntimeApplication(); Thread.currentThread().setContextClassLoader(startupAction.getClassLoader()); populateDeepCloneField(startupAction); @@ -339,9 +344,10 @@ public void beforeEach(ExtensionContext context) throws Exception { beforeEachCallback.getClass().getMethod("beforeEach", tuple.getKey()) .invoke(beforeEachCallback, tuple.getValue()); } + String endpointPath = getEndpointPath(context, testHttpEndpointProviders); if (runningQuarkusApplication != null) { runningQuarkusApplication.getClassLoader().loadClass(RestAssuredURLManager.class.getName()) - .getDeclaredMethod("setURL", boolean.class).invoke(null, false); + .getDeclaredMethod("setURL", boolean.class, String.class).invoke(null, false, endpointPath); runningQuarkusApplication.getClassLoader().loadClass(TestScopeManager.class.getName()) .getDeclaredMethod("setup", boolean.class).invoke(null, false); } @@ -354,6 +360,38 @@ public void beforeEach(ExtensionContext context) throws Exception { } } + public static String getEndpointPath(ExtensionContext context, List, String>> testHttpEndpointProviders) { + String endpointPath = null; + TestHTTPEndpoint testHTTPEndpoint = context.getRequiredTestMethod().getAnnotation(TestHTTPEndpoint.class); + if (testHTTPEndpoint == null) { + Class clazz = context.getRequiredTestClass(); + while (true) { + // go up the hierarchy because most Native tests extend from a regular Quarkus test + testHTTPEndpoint = clazz.getAnnotation(TestHTTPEndpoint.class); + if (testHTTPEndpoint != null) { + break; + } + clazz = clazz.getSuperclass(); + if (clazz == Object.class) { + break; + } + } + } + if (testHTTPEndpoint != null) { + for (Function, String> i : testHttpEndpointProviders) { + endpointPath = i.apply(testHTTPEndpoint.value()); + if (endpointPath != null) { + break; + } + } + if (endpointPath == null) { + throw new RuntimeException("Cannot determine HTTP path for endpoint " + testHTTPEndpoint.value() + + " for test method " + context.getRequiredTestMethod()); + } + } + return endpointPath; + } + @Override public void afterEach(ExtensionContext context) throws Exception { if (isNativeTest(context)) { @@ -574,7 +612,8 @@ private void initTestState(ExtensionContext extensionContext, ExtensionState sta actualTestInstance = runningQuarkusApplication.instance(actualTestClass); Class resM = Thread.currentThread().getContextClassLoader().loadClass(TestHTTPResourceManager.class.getName()); - resM.getDeclaredMethod("inject", Object.class).invoke(null, actualTestInstance); + resM.getDeclaredMethod("inject", Object.class, List.class).invoke(null, actualTestInstance, + testHttpEndpointProviders); state.testResourceManager.getClass().getMethod("inject", Object.class).invoke(state.testResourceManager, actualTestInstance); for (Object beforeAllCallback : beforeAllCallbacks) {