Skip to content

Commit c52fdaa

Browse files
committed
add test web client field injection:
- @webclient for ClientSupport, @webclient(APP), @webclient(ADMIN), @webclient(REST) for specific clients - @WebResourceClient for resource client direct mapping (works for integration and stub rest tests)
1 parent bb93e35 commit c52fdaa

17 files changed

Lines changed: 719 additions & 1 deletion

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@
4343
- (BREAKING) deprecated targetMain(): replaced with targetApp()
4444
- (BREAKING) StubRest default status declaration removed as not useful
4545
(required status could be declared now with the new request builder)
46+
* Add test web client field injection:
47+
- @WebClient for ClientSupport, @WebClient(APP), @WebClient(ADMIN), @WebClient(REST) for specific clients
48+
- @WebResourceClient for resource client direct mapping (works for integration and stub rest tests)
4649
* Add guicey event ApplicationStartingEvent thrown just before managed and web services startup
4750
* Fix stubs rest too early startup, causing problems with jersey registrations in application run method
4851

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package ru.vyarus.dropwizard.guice.test.jupiter.ext.client;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
/**
9+
* Inject web client (derived from {@link ru.vyarus.dropwizard.guice.test.ClientSupport}) into test field.
10+
* <ul>
11+
* <li>{@code @WebClient ClientSupport client}</li>
12+
* <li>{@code @WebClient(APP) TestClient app} - same as {@code client.appClient()}</li>
13+
* <li>{@code @WebClient(ADMIN) TestClient admin} - same as {@code client.adminClient()}</li>
14+
* <li>{@code @WebClient(REST) TestClient rest} - same as {@code client.restClient()}</li>
15+
* </ul>
16+
*
17+
* @author Vyacheslav Rusakov
18+
* @since 15.10.2025
19+
* @see ru.vyarus.dropwizard.guice.test.jupiter.ext.client.rest.WebResourceClient for direct resource client injection
20+
*/
21+
@Retention(RetentionPolicy.RUNTIME)
22+
@Target(ElementType.FIELD)
23+
public @interface WebClient {
24+
25+
/**
26+
* @return client type (default for root object and other for specific clients)
27+
*/
28+
WebClientType value() default WebClientType.Support;
29+
30+
/**
31+
* Reset client defaults after each test method. Enabled by default.
32+
*
33+
* @return false to disable defaults reset
34+
*/
35+
boolean autoReset() default true;
36+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package ru.vyarus.dropwizard.guice.test.jupiter.ext.client;
2+
3+
import org.junit.jupiter.api.extension.ExtensionContext;
4+
import ru.vyarus.dropwizard.guice.test.ClientSupport;
5+
import ru.vyarus.dropwizard.guice.test.client.TestClient;
6+
import ru.vyarus.dropwizard.guice.test.jupiter.env.TestExtension;
7+
import ru.vyarus.dropwizard.guice.test.jupiter.env.field.AnnotatedField;
8+
import ru.vyarus.dropwizard.guice.test.jupiter.env.field.AnnotatedTestFieldSetup;
9+
import ru.vyarus.dropwizard.guice.test.jupiter.env.listen.EventContext;
10+
11+
import java.util.List;
12+
13+
/**
14+
* {@link ru.vyarus.dropwizard.guice.test.jupiter.ext.client.WebClient} field support implementation.
15+
* <p>
16+
* Injects either root {@link ru.vyarus.dropwizard.guice.test.ClientSupport} object or one of specific clients.
17+
*
18+
* @author Vyacheslav Rusakov
19+
* @since 17.10.2025
20+
*/
21+
public class WebClientFieldsSupport extends AnnotatedTestFieldSetup<WebClient, TestClient> {
22+
23+
private static final String TEST_CLIENT_FIELDS = "TEST_CLIENT_FIELDS";
24+
25+
26+
public WebClientFieldsSupport() {
27+
super(WebClient.class, TestClient.class, TEST_CLIENT_FIELDS);
28+
}
29+
30+
@Override
31+
protected void fieldDetected(final ExtensionContext context,
32+
final AnnotatedField<WebClient, TestClient> field) {
33+
final WebClientType type = field.getAnnotation().value();
34+
if (WebClientType.Support.equals(type) && !ClientSupport.class.equals(field.getType())) {
35+
throw new IllegalStateException("ClientSupport type must be used for the default @WebClient field: "
36+
+ field.toStringField());
37+
}
38+
}
39+
40+
@Override
41+
protected void registerHooks(final TestExtension extension) {
42+
// nothing
43+
}
44+
45+
@Override
46+
protected <K> void initializeField(final AnnotatedField<WebClient, TestClient> field,
47+
final TestClient userValue) {
48+
// nothing
49+
}
50+
51+
@Override
52+
protected void beforeValueInjection(final EventContext context,
53+
final AnnotatedField<WebClient, TestClient> field) {
54+
// nothing
55+
}
56+
57+
@Override
58+
protected TestClient injectFieldValue(final EventContext context,
59+
final AnnotatedField<WebClient, TestClient> field) {
60+
final ClientSupport support = context.getClient();
61+
return switch (field.getAnnotation().value()) {
62+
case Support -> support;
63+
case App -> support.appClient();
64+
case Admin -> support.adminClient();
65+
case Rest -> support.restClient();
66+
};
67+
}
68+
69+
@Override
70+
protected void report(final EventContext context,
71+
final List<AnnotatedField<WebClient, TestClient>> annotatedFields) {
72+
// no reports required for web client fields
73+
}
74+
75+
@Override
76+
protected void beforeTest(final EventContext context,
77+
final AnnotatedField<WebClient, TestClient> field,
78+
final TestClient value) {
79+
// not used
80+
}
81+
82+
@Override
83+
protected void afterTest(final EventContext context,
84+
final AnnotatedField<WebClient, TestClient> field,
85+
final TestClient value) {
86+
// reset client defaults
87+
if (field.getAnnotation().autoReset()) {
88+
value.reset();
89+
}
90+
}
91+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package ru.vyarus.dropwizard.guice.test.jupiter.ext.client;
2+
3+
/**
4+
* Client type for {@link ru.vyarus.dropwizard.guice.test.jupiter.ext.client.WebClient} annotation.
5+
*
6+
* @author Vyacheslav Rusakov
7+
* @since 15.10.2025
8+
*/
9+
public enum WebClientType {
10+
/**
11+
* Root {@link ru.vyarus.dropwizard.guice.test.ClientSupport} client (used to obtain other clients).
12+
* This is also a client, targeted application root (may be different from actual application mapping).
13+
*/
14+
Support,
15+
/**
16+
* Application client ({@link ru.vyarus.dropwizard.guice.test.ClientSupport#appClient()}).
17+
*/
18+
App,
19+
/**
20+
* Admin client ({@link ru.vyarus.dropwizard.guice.test.ClientSupport#adminClient()}).
21+
*/
22+
Admin,
23+
/**
24+
* Rest client ({@link ru.vyarus.dropwizard.guice.test.ClientSupport#restClient()}).
25+
*/
26+
Rest
27+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package ru.vyarus.dropwizard.guice.test.jupiter.ext.client.rest;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
/**
9+
* Inject client for exact resource class. The same as {@code ClientSupport.restClient(Resource.class)}.
10+
* <p>
11+
* Usage: {@code @WebResourceClient ResourceClient<Resource> rest}.
12+
* <p>
13+
* The same could be achieved with the root rest client (or client support object):
14+
* <pre>{@code @WebClient(REST) TestClient rest;
15+
* ResourceClient res = rest.restClient(Resource.class)
16+
* }</pre>
17+
* <p>
18+
* Works with both integration tests and stub rest.
19+
*
20+
* @author Vyacheslav Rusakov
21+
* @since 17.10.2025
22+
*/
23+
@Retention(RetentionPolicy.RUNTIME)
24+
@Target(ElementType.FIELD)
25+
public @interface WebResourceClient {
26+
27+
/**
28+
* Reset client defaults after each test method. Enabled by default.
29+
*
30+
* @return false to disable defaults reset
31+
*/
32+
boolean autoReset() default true;
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package ru.vyarus.dropwizard.guice.test.jupiter.ext.client.rest;
2+
3+
import org.junit.jupiter.api.extension.ExtensionContext;
4+
import ru.vyarus.dropwizard.guice.test.client.ResourceClient;
5+
import ru.vyarus.dropwizard.guice.test.jupiter.env.TestExtension;
6+
import ru.vyarus.dropwizard.guice.test.jupiter.env.field.AnnotatedField;
7+
import ru.vyarus.dropwizard.guice.test.jupiter.env.field.AnnotatedTestFieldSetup;
8+
import ru.vyarus.dropwizard.guice.test.jupiter.env.listen.EventContext;
9+
import ru.vyarus.dropwizard.guice.test.jupiter.ext.rest.RestStubFieldsSupport;
10+
11+
import java.util.List;
12+
13+
/**
14+
* {@link ru.vyarus.dropwizard.guice.test.jupiter.ext.client.rest.WebResourceClient} field support implementation.
15+
* <p>
16+
* Works with both integration tests and stubs rest.
17+
*
18+
* @author Vyacheslav Rusakov
19+
* @since 17.10.2025
20+
*/
21+
public class WebResourceClientFieldsSupport extends AnnotatedTestFieldSetup<WebResourceClient, ResourceClient> {
22+
private static final String TEST_REST_CLIENT_FIELDS = "TEST_REST_CLIENT_FIELDS";
23+
24+
public WebResourceClientFieldsSupport() {
25+
super(WebResourceClient.class, ResourceClient.class, TEST_REST_CLIENT_FIELDS);
26+
}
27+
28+
@Override
29+
protected void fieldDetected(final ExtensionContext context,
30+
final AnnotatedField<WebResourceClient, ResourceClient> field) {
31+
final Class<?> generic = field.getTypeParameters().get(0);
32+
if (Object.class.equals(generic)) {
33+
throw new IllegalStateException("Target resource class must be specified"
34+
+ " as generic (ResourceClient<RestClass>) in field: " + field.toStringField());
35+
}
36+
}
37+
38+
@Override
39+
protected void registerHooks(final TestExtension extension) {
40+
// nothing
41+
}
42+
43+
@Override
44+
protected <K> void initializeField(final AnnotatedField<WebResourceClient, ResourceClient> field,
45+
final ResourceClient userValue) {
46+
// nothing
47+
}
48+
49+
@Override
50+
protected void beforeValueInjection(final EventContext context,
51+
final AnnotatedField<WebResourceClient, ResourceClient> field) {
52+
// not used
53+
}
54+
55+
@Override
56+
protected ResourceClient injectFieldValue(final EventContext context,
57+
final AnnotatedField<WebResourceClient, ResourceClient> field) {
58+
final Class<?> resource = field.getTypeParameters().get(0);
59+
if (context.isWebStarted()) {
60+
// integration test
61+
return context.getClient().restClient(resource);
62+
} else {
63+
// rest stubs test
64+
return RestStubFieldsSupport.lookupRestClient(context.getJunitContext())
65+
.orElseThrow(() -> new IllegalStateException(String.format(
66+
"Resource client can't be used under lightweight guicey test without @StubRest: "
67+
+ field.toStringField())))
68+
.restClient(resource);
69+
}
70+
}
71+
72+
@Override
73+
protected void report(final EventContext context,
74+
final List<AnnotatedField<WebResourceClient, ResourceClient>> annotatedFields) {
75+
// not needed
76+
}
77+
78+
@Override
79+
protected void beforeTest(final EventContext context,
80+
final AnnotatedField<WebResourceClient, ResourceClient> field,
81+
final ResourceClient value) {
82+
// nothing
83+
}
84+
85+
@Override
86+
protected void afterTest(final EventContext context,
87+
final AnnotatedField<WebResourceClient, ResourceClient> field,
88+
final ResourceClient value) {
89+
// reset client defaults
90+
if (field.getAnnotation().autoReset()) {
91+
value.reset();
92+
}
93+
}
94+
}

dropwizard-guicey/src/main/java/ru/vyarus/dropwizard/guice/test/jupiter/ext/rest/RestStubFieldsSupport.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.util.Collections;
2020
import java.util.List;
21+
import java.util.Optional;
2122

2223
/**
2324
* {@link ru.vyarus.dropwizard.guice.test.jupiter.ext.rest.StubRest} field support implementation.
@@ -43,6 +44,7 @@ public class RestStubFieldsSupport extends AnnotatedTestFieldSetup<StubRest, Res
4344
implements GuiceyConfigurationHook {
4445

4546
private static final String TEST_RESOURCES_FIELD = "TEST_RESOURCES";
47+
private static final String STUB_REST_CLIENT_KEY = "STUB_REST_CLIENT_KEY";
4648
private RestStubsHook restStubs;
4749

4850
/**
@@ -52,6 +54,21 @@ public RestStubFieldsSupport() {
5254
super(StubRest.class, RestClient.class, TEST_RESOURCES_FIELD);
5355
}
5456

57+
/**
58+
* Static lookup for registered stubs rest client. Might be used by other extensions to get access to
59+
* rest client.
60+
* <p>
61+
* Note: stubs rest client is registered AFTER application startup.
62+
*
63+
* @param context junit context
64+
* @return rest client or null if not registered or requested too early
65+
*/
66+
public static Optional<RestClient> lookupRestClient(final ExtensionContext context) {
67+
return Optional.ofNullable((RestClient) context
68+
.getStore(ExtensionContext.Namespace.create(RestStubFieldsSupport.class))
69+
.get(STUB_REST_CLIENT_KEY));
70+
}
71+
5572
@Override
5673
public void configure(final GuiceBundle.Builder builder) throws Exception {
5774
builder.onGuiceyStartup((config, env, injector) ->
@@ -85,7 +102,8 @@ protected <K> void initializeField(final AnnotatedField<StubRest, RestClient> fi
85102
@Override
86103
protected void beforeValueInjection(final EventContext context,
87104
final AnnotatedField<StubRest, RestClient> field) {
88-
// not used
105+
context.getJunitContext().getStore(ExtensionContext.Namespace.create(RestStubFieldsSupport.class))
106+
.put(STUB_REST_CLIENT_KEY, restStubs.getRestClient());
89107
}
90108

91109
@Override

dropwizard-guicey/src/main/resources/META-INF/services/ru.vyarus.dropwizard.guice.test.jupiter.env.TestEnvironmentSetup

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
ru.vyarus.dropwizard.guice.test.jupiter.ext.client.WebClientFieldsSupport
2+
ru.vyarus.dropwizard.guice.test.jupiter.ext.client.rest.WebResourceClientFieldsSupport
13
ru.vyarus.dropwizard.guice.test.jupiter.ext.log.LogFieldsSupport
24
ru.vyarus.dropwizard.guice.test.jupiter.ext.rest.RestStubFieldsSupport
35
ru.vyarus.dropwizard.guice.test.jupiter.ext.stub.StubFieldsSupport
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package ru.vyarus.dropwizard.guice.test.jupiter.setup.client;
2+
3+
import org.junit.jupiter.api.Test;
4+
import ru.vyarus.dropwizard.guice.support.DefaultTestApp;
5+
import ru.vyarus.dropwizard.guice.test.ClientSupport;
6+
import ru.vyarus.dropwizard.guice.test.client.TestClient;
7+
import ru.vyarus.dropwizard.guice.test.jupiter.TestDropwizardApp;
8+
import ru.vyarus.dropwizard.guice.test.jupiter.ext.client.WebClient;
9+
import ru.vyarus.dropwizard.guice.test.jupiter.ext.client.WebClientType;
10+
11+
import static org.assertj.core.api.Assertions.assertThat;
12+
13+
/**
14+
* @author Vyacheslav Rusakov
15+
* @since 17.10.2025
16+
*/
17+
@TestDropwizardApp(value = DefaultTestApp.class, configOverride = {
18+
"server.applicationContextPath: /app",
19+
"server.adminContextPath: /admin",
20+
}, restMapping = "api")
21+
public class ClientInjectionTest {
22+
23+
@WebClient
24+
ClientSupport client;
25+
26+
@WebClient(WebClientType.App)
27+
TestClient<?> app;
28+
29+
@WebClient(WebClientType.Admin)
30+
TestClient<?> admin;
31+
32+
@WebClient(WebClientType.Rest)
33+
TestClient<?> rest;
34+
35+
@Test
36+
void testClientInjection(ClientSupport support) {
37+
assertThat(app).isNotNull();
38+
assertThat(admin).isNotNull();
39+
assertThat(rest).isNotNull();
40+
41+
assertThat(client).isNotNull().isSameAs(support);
42+
assertThat(app.getBaseUri()).isEqualTo(support.appClient().getBaseUri());
43+
assertThat(admin.getBaseUri()).isEqualTo(support.adminClient().getBaseUri());
44+
assertThat(rest.getBaseUri()).isEqualTo(support.restClient().getBaseUri());
45+
46+
}
47+
}

0 commit comments

Comments
 (0)