Skip to content

Commit

Permalink
Merge pull request quarkusio#11080 from stuartwdouglas/test-resources
Browse files Browse the repository at this point in the history
Add ability to directly specify endpoints in tests
  • Loading branch information
gsmet authored Jul 31, 2020
2 parents f81e7e9 + fddd2e1 commit 95ff1cc
Show file tree
Hide file tree
Showing 20 changed files with 465 additions and 17 deletions.
Original file line number Diff line number Diff line change
@@ -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<Class<?>, String> endpointProvider();

static List<Function<Class<?>, String>> load() {
List<Function<Class<?>, String>> ret = new ArrayList<>();
for (TestHttpEndpointProvider i : ServiceLoader.load(TestHttpEndpointProvider.class,
Thread.currentThread().getContextClassLoader())) {
ret.add(i.endpointProvider());
}
return ret;
}

}
112 changes: 112 additions & 0 deletions docs/src/main/asciidoc/getting-started-testing.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Class<?>, String> endpointProvider() {
return new Function<Class<?>, 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<String> 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;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
io.quarkus.resteasy.server.common.runtime.RESTEasyTestHttpProvider
Original file line number Diff line number Diff line change
@@ -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<Class<?>, String> endpointProvider() {
return new Function<Class<?>, 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<String> 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;
}
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
io.quarkus.undertow.runtime.UndertowTestHttpProvider
Original file line number Diff line number Diff line change
@@ -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<Class<?>, String> endpointProvider() {
return new Function<Class<?>, 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;
}
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
io.quarkus.vertx.web.runtime.ReactiveRoutesTestHttpProvider
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import javax.ws.rs.Path;

@Path("/ft")
public class TestResource {
public class FaultToleranceTestResource {

@Inject
Service service;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import javax.ws.rs.GET;
import javax.ws.rs.Path;

@Path("/txn")
@Path("/txn/txendpoint")
public class TransactionResource {

@Inject
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,18 @@

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;

@QuarkusTest
public class ServletTestCase {

@Test
@TestHTTPEndpoint(TestServlet.class)
public void testServlet() {
RestAssured.when().get("/testservlet").then()
RestAssured.when().get().then()
.body(is("A message"));
}

Expand Down
Loading

0 comments on commit 95ff1cc

Please sign in to comment.