Skip to content

Commit

Permalink
Add ability to directly specify endpoints in tests
Browse files Browse the repository at this point in the history
This makes it easy to specify eactly what endpoint is being
tested.
  • Loading branch information
stuartwdouglas committed Jul 31, 2020
1 parent 858851b commit bc68117
Show file tree
Hide file tree
Showing 17 changed files with 422 additions and 13 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 and Servlets. 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
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
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
}

Expand Down
Loading

0 comments on commit bc68117

Please sign in to comment.