Skip to content

Commit edd362c

Browse files
authored
4.x: Use SetUpFeatures annotation (helidon-io#10037)
* implemented `SetUpFeatures` annotation processing * added a test for both ServerTest and RoutingTest * updated documentation
1 parent 507f05c commit edd362c

7 files changed

Lines changed: 162 additions & 49 deletions

File tree

docs/src/main/asciidoc/se/testing.adoc

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
///////////////////////////////////////////////////////////////////////////////
22

3-
Copyright (c) 2023, 2024 Oracle and/or its affiliates.
3+
Copyright (c) 2023, 2025 Oracle and/or its affiliates.
44

55
Licensed under the Apache License, Version 2.0 (the "License");
66
you may not use this file except in compliance with the License.
@@ -101,7 +101,11 @@ and may have any name. The `@SetUpRoute` annotation has `value` with socket name
101101
102102
|===
103103
104-
In addition, a static method annotated with `@SetUpServer` can be defined for `@ServerTest`, which has a single parameter of link:{javadoc-base-url}/io.helidon.webserver/io/helidon/webserver/WebServerConfig.Builder.html[`WebServerConfig.Builder`].
104+
In addition:
105+
106+
- a static method annotated with `@SetUpServer` can be defined for tests, which has a single parameter of link:{javadoc-base-url}/io.helidon.webserver/io/helidon/webserver/WebServerConfig.Builder.html[`WebServerConfig.Builder`].
107+
- a static method annotated with `@SetUpFeatures` can be defined for tests, which returns `List<? extends ServerFeature>` to configure additional features, or update discovered features, feature discovery can be disabled using the annotation `value()``
108+
105109
106110
The following table lists the injectable types (through constructor or method injection).
107111

webserver/testing/junit5/junit5/src/main/java/io/helidon/webserver/testing/junit5/HelidonRoutingJunitExtension.java

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,6 @@
4242
import org.junit.jupiter.api.extension.ParameterResolutionException;
4343
import org.junit.jupiter.api.extension.ParameterResolver;
4444

45-
import static io.helidon.webserver.testing.junit5.Junit5Util.withStaticMethods;
46-
4745
/**
4846
* JUnit5 extension to support Helidon WebServer in tests.
4947
*/
@@ -80,7 +78,9 @@ public void beforeAll(ExtensionContext context) {
8078

8179
extensions.forEach(it -> it.beforeAll(context));
8280

81+
setupFeatures(builder);
8382
setupServer(builder);
83+
8484
serverConfig = builder.buildPrototype();
8585

8686
initRoutings();
@@ -193,26 +193,6 @@ private SetUpRouteHandler createRoutingMethodCall(List<ServerFeature> features,
193193
};
194194
}
195195

196-
private void setupServer(WebServerConfig.Builder builder) {
197-
withStaticMethods(testClass(), SetUpServer.class, (setUpServer, method) -> {
198-
Class<?>[] parameterTypes = method.getParameterTypes();
199-
if (parameterTypes.length != 1) {
200-
throw new IllegalArgumentException("Method " + method + " annotated with " + SetUpServer.class.getSimpleName()
201-
+ " does not have exactly one parameter (WebServerConfig.Builder)");
202-
}
203-
if (!parameterTypes[0].equals(WebServerConfig.Builder.class)) {
204-
throw new IllegalArgumentException("Method " + method + " annotated with " + SetUpServer.class.getSimpleName()
205-
+ " does not have exactly one parameter (WebServerConfig.Builder)");
206-
}
207-
try {
208-
method.setAccessible(true);
209-
method.invoke(null, builder);
210-
} catch (IllegalAccessException | InvocationTargetException e) {
211-
throw new IllegalStateException("Could not invoke method " + method, e);
212-
}
213-
});
214-
}
215-
216196
private interface SetUpRouteHandler {
217197
void handle(String socketName);
218198
}

webserver/testing/junit5/junit5/src/main/java/io/helidon/webserver/testing/junit5/HelidonServerJunitExtension.java

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ public void beforeAll(ExtensionContext context) {
106106
builder.port(0)
107107
.shutdownHook(false);
108108

109+
setupFeatures(builder);
109110
setupServer(builder);
110111
addRouting(builder);
111112

@@ -234,26 +235,6 @@ private URI uri(Executable declaringExecutable, String socketName) {
234235
return uri;
235236
}
236237

237-
private void setupServer(WebServerConfig.Builder builder) {
238-
withStaticMethods(testClass(), SetUpServer.class, (setUpServer, method) -> {
239-
Class<?>[] parameterTypes = method.getParameterTypes();
240-
if (parameterTypes.length != 1) {
241-
throw new IllegalArgumentException("Method " + method + " annotated with " + SetUpServer.class.getSimpleName()
242-
+ " does not have exactly one parameter (WebServerConfig.Builder)");
243-
}
244-
if (!parameterTypes[0].equals(WebServerConfig.Builder.class)) {
245-
throw new IllegalArgumentException("Method " + method + " annotated with " + SetUpServer.class.getSimpleName()
246-
+ " does not have exactly one parameter (WebServerConfig.Builder)");
247-
}
248-
try {
249-
method.setAccessible(true);
250-
method.invoke(null, builder);
251-
} catch (IllegalAccessException | InvocationTargetException e) {
252-
throw new IllegalStateException("Could not invoke method " + method, e);
253-
}
254-
});
255-
}
256-
257238
private void addRouting(WebServerConfig.Builder builder) {
258239
Map<String, ListenerConfig.Builder> listenerConfigs = new HashMap<>();
259240
Map<String, Router.Builder> routerBuilders = new HashMap<>();

webserver/testing/junit5/junit5/src/main/java/io/helidon/webserver/testing/junit5/JunitExtensionBase.java

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,21 @@
1616

1717
package io.helidon.webserver.testing.junit5;
1818

19+
import java.lang.reflect.InvocationTargetException;
1920
import java.lang.reflect.Method;
2021
import java.lang.reflect.Modifier;
2122
import java.util.ArrayList;
2223
import java.util.List;
2324

2425
import io.helidon.testing.junit5.TestJunitExtension;
26+
import io.helidon.webserver.WebServerConfig;
27+
import io.helidon.webserver.spi.ServerFeature;
2528

2629
import org.junit.jupiter.api.extension.AfterAllCallback;
2730
import org.junit.jupiter.api.extension.ExtensionContext;
2831

32+
import static io.helidon.webserver.testing.junit5.Junit5Util.withStaticMethods;
33+
2934
abstract class JunitExtensionBase extends TestJunitExtension implements AfterAllCallback {
3035
private Class<?> testClass;
3136

@@ -38,6 +43,75 @@ public void afterAll(ExtensionContext extensionContext) {
3843
super.afterAll(extensionContext);
3944
}
4045

46+
void setupServer(WebServerConfig.Builder builder) {
47+
withStaticMethods(testClass(), SetUpServer.class, (setUpServer, method) -> {
48+
Class<?>[] parameterTypes = method.getParameterTypes();
49+
if (parameterTypes.length != 1) {
50+
throw new IllegalArgumentException("Method " + method + " annotated with " + SetUpServer.class.getSimpleName()
51+
+ " does not have exactly one parameter (WebServerConfig.Builder)");
52+
}
53+
if (!parameterTypes[0].equals(WebServerConfig.Builder.class)) {
54+
throw new IllegalArgumentException("Method " + method + " annotated with " + SetUpServer.class.getSimpleName()
55+
+ " does not have exactly one parameter (WebServerConfig.Builder)");
56+
}
57+
if (!Modifier.isStatic(method.getModifiers())) {
58+
throw new IllegalArgumentException("Method " + method + " annotated with " + SetUpServer.class.getSimpleName()
59+
+ " is not static");
60+
}
61+
try {
62+
method.setAccessible(true);
63+
method.invoke(null, builder);
64+
} catch (IllegalAccessException | InvocationTargetException e) {
65+
throw new IllegalStateException("Could not invoke method " + method, e);
66+
}
67+
});
68+
}
69+
70+
@SuppressWarnings("unchecked")
71+
void setupFeatures(WebServerConfig.Builder builder) {
72+
withStaticMethods(testClass(), SetUpFeatures.class, ((setUpFeatures, method) -> {
73+
if (!setUpFeatures.value()) {
74+
builder.featuresDiscoverServices(false);
75+
}
76+
Class<?>[] parameterTypes = method.getParameterTypes();
77+
if (parameterTypes.length != 0) {
78+
throw new IllegalArgumentException("Method " + method + " annotated with " + SetUpFeatures.class.getSimpleName()
79+
+ " has parameter(s), which is not allowed. It should return "
80+
+ " List<ServerFeature>.");
81+
}
82+
if (!Modifier.isStatic(method.getModifiers())) {
83+
throw new IllegalArgumentException("Method " + method + " annotated with " + SetUpFeatures.class.getSimpleName()
84+
+ " is not static");
85+
}
86+
Object result;
87+
try {
88+
method.setAccessible(true);
89+
result = method.invoke(null);
90+
} catch (IllegalAccessException | InvocationTargetException e) {
91+
throw new IllegalStateException("Could not invoke method " + method, e);
92+
}
93+
94+
List<ServerFeature> features;
95+
try {
96+
features = (List<ServerFeature>) result;
97+
} catch (ClassCastException e) {
98+
throw new IllegalArgumentException("Method " + method + " annotated with " + SetUpFeatures.class.getSimpleName()
99+
+ " returned a result that is not a List. Supported is "
100+
+ "List<? extends ServerFeature>.", e);
101+
}
102+
try {
103+
for (ServerFeature feature : features) {
104+
builder.addFeature(feature);
105+
}
106+
} catch (ClassCastException e) {
107+
throw new IllegalArgumentException("Method " + method + " annotated with " + SetUpFeatures.class.getSimpleName()
108+
+ " returned a result that is a List, but an element was not "
109+
+ "a ServerFeature.", e);
110+
}
111+
}));
112+
}
113+
114+
41115
void testClass(Class<?> testClass) {
42116
this.testClass = testClass;
43117
}

webserver/testing/junit5/junit5/src/main/java/io/helidon/webserver/testing/junit5/SetUpFeatures.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2020, 2023 Oracle and/or its affiliates.
2+
* Copyright (c) 2020, 2025 Oracle and/or its affiliates.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -24,7 +24,10 @@
2424
* A static method configuring server features.
2525
* <p>
2626
* Supported signatures:
27-
* {@code static List<? extends ServerFeature> features()}
27+
* {@code static List<? extends ServerFeature> features()}.
28+
* <p>
29+
* Method(s) annotated with this annotation will be invoked before methods annotated with
30+
* {@link io.helidon.webserver.testing.junit5.SetUpServer}.
2831
*/
2932
@Retention(RetentionPolicy.RUNTIME)
3033
@Target(ElementType.METHOD)

webserver/testing/junit5/junit5/src/test/java/io/helidon/webserver/testing/junit5/TestRoutingTest.java

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2022, 2023 Oracle and/or its affiliates.
2+
* Copyright (c) 2022, 2025 Oracle and/or its affiliates.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -21,11 +21,13 @@
2121
import java.security.cert.X509Certificate;
2222
import java.util.LinkedList;
2323
import java.util.List;
24+
import java.util.concurrent.atomic.AtomicBoolean;
2425

2526
import io.helidon.http.Method;
2627
import io.helidon.http.Status;
2728
import io.helidon.webclient.http1.Http1ClientResponse;
2829
import io.helidon.webserver.http.HttpRouting;
30+
import io.helidon.webserver.spi.ServerFeature;
2931

3032
import org.junit.jupiter.api.Test;
3133

@@ -88,6 +90,16 @@ static void adminRouting(HttpRouting.Builder router) {
8890
router.get("/get", (req, res) -> res.send(ADMIN_ENTITY));
8991
}
9092

93+
@SetUpFeatures
94+
static List<ServerFeature> features() {
95+
return List.of(new TestFeature());
96+
}
97+
98+
@Test
99+
void testFeatureSetUp() {
100+
assertThat(TestFeature.isSetUp(), is(true));
101+
}
102+
91103
@Test
92104
void testGet() {
93105
String response = client.get("/get")
@@ -148,4 +160,27 @@ public String getName() {
148160
return name;
149161
}
150162
}
163+
164+
private static class TestFeature implements ServerFeature {
165+
private static final AtomicBoolean SET_UP = new AtomicBoolean();
166+
167+
@Override
168+
public void setup(ServerFeatureContext featureContext) {
169+
SET_UP.set(true);
170+
}
171+
172+
@Override
173+
public String name() {
174+
return "test";
175+
}
176+
177+
@Override
178+
public String type() {
179+
return "test";
180+
}
181+
182+
static boolean isSetUp() {
183+
return SET_UP.get();
184+
}
185+
}
151186
}

webserver/testing/junit5/junit5/src/test/java/io/helidon/webserver/testing/junit5/TestServerTest.java

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2022, 2023 Oracle and/or its affiliates.
2+
* Copyright (c) 2022, 2025 Oracle and/or its affiliates.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,14 +17,17 @@
1717
package io.helidon.webserver.testing.junit5;
1818

1919
import java.net.URI;
20+
import java.util.List;
21+
import java.util.concurrent.atomic.AtomicBoolean;
2022

2123
import io.helidon.common.context.Context;
2224
import io.helidon.common.testing.http.junit5.SocketHttpClient;
2325
import io.helidon.webclient.http1.Http1Client;
2426
import io.helidon.webserver.ListenerConfig;
25-
import io.helidon.webserver.WebServerConfig;
2627
import io.helidon.webserver.WebServer;
28+
import io.helidon.webserver.WebServerConfig;
2729
import io.helidon.webserver.http.HttpRouting;
30+
import io.helidon.webserver.spi.ServerFeature;
2831

2932
import org.junit.jupiter.api.Test;
3033

@@ -55,6 +58,11 @@ class TestServerTest {
5558
this.customUri = customUri;
5659
}
5760

61+
@SetUpFeatures
62+
static List<ServerFeature> features() {
63+
return List.of(new TestFeature());
64+
}
65+
5866
@SetUpServer
5967
static void setUp(WebServerConfig.Builder builder) {
6068
Context serverContext = Context.create();
@@ -73,6 +81,11 @@ static void routing2(HttpRouting.Builder r, ListenerConfig.Builder l) {
7381
l.writeQueueLength(10);
7482
}
7583

84+
@Test
85+
void testFeatureSetUp() {
86+
assertThat(TestFeature.isSetUp(), is(true));
87+
}
88+
7689
@Test
7790
void testSetUpCalled() {
7891
assertThat(server.port("socket"), greaterThan(0));
@@ -164,4 +177,27 @@ void testUriInjectedParameter(URI uri) {
164177
assertThat(uri.getHost(), is("localhost"));
165178
assertThat(uri.getPort(), is(server.port()));
166179
}
180+
181+
private static class TestFeature implements ServerFeature {
182+
private static final AtomicBoolean SET_UP = new AtomicBoolean();
183+
184+
@Override
185+
public void setup(ServerFeatureContext featureContext) {
186+
SET_UP.set(true);
187+
}
188+
189+
@Override
190+
public String name() {
191+
return "test";
192+
}
193+
194+
@Override
195+
public String type() {
196+
return "test";
197+
}
198+
199+
static boolean isSetUp() {
200+
return SET_UP.get();
201+
}
202+
}
167203
}

0 commit comments

Comments
 (0)