Skip to content

Commit 7df5891

Browse files
committed
merge TestRestClient into TestClient to simplify usage;
rename customClient() into externalClient(); rename subClient(Class) to restClient(Class) (uniform with ClientSupport)
1 parent a836454 commit 7df5891

11 files changed

Lines changed: 87 additions & 183 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@
1919
* Unify ClientSupport and StubRest client APIs
2020
- New api is a wrapper above jersey client api to simplify test-specific configuration and validation
2121
(jersey api is still available). The request builder unifies all possible configurations in one place.
22-
- New common base class TestClient (and TestRestClient for rest clients, adding rest-specific methods)
22+
- New common base client class TestClient
2323
- ClientSupport is a TestClient, but also could provide 3 special clients: appClient(), adminClient(), restClient()
2424
(restClient() is the same as StubRest client)
2525
- New sub clients could be created by applying additional path segments:
2626
client.subClient("/sub/path/)
27-
- External api client could be created with support.customClient("som external url")
27+
- External api client could be created with support.externalClient("som external url")
2828
- New client rest api based on real method calls: restClient(RestClass.class).method(mock -> mock.restMethod(args)).invoke()
2929
(target path and method type resolved from annotations, arguments used for request configuration)
3030
- Helper api for testing multipart requests: restClient(..).multipartMethod(..)

dropwizard-guicey/src/main/java/ru/vyarus/dropwizard/guice/test/ClientSupport.java

Lines changed: 8 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
import ru.vyarus.dropwizard.guice.test.client.ResourceClient;
1212
import ru.vyarus.dropwizard.guice.test.client.TestClient;
1313
import ru.vyarus.dropwizard.guice.test.client.TestClientFactory;
14-
import ru.vyarus.dropwizard.guice.test.client.TestRestClient;
1514
import ru.vyarus.dropwizard.guice.test.client.builder.TestRequestConfig;
1615
import ru.vyarus.dropwizard.guice.url.AppUrlBuilder;
1716

@@ -45,9 +44,9 @@
4544
* The main idea of clients is the ability to create a client for any base path to shorten urls in tests.
4645
* Also, each client could declare its own defaults, applied to all requests (for example, useful for authorization).
4746
* <p>
48-
* There is also a specialized {@link #customClient(String, Object...)} for custom clients for creating remote api
47+
* There is also a specialized {@link #externalClient(String, Object...)} for custom clients for creating remote api
4948
* clients (with the same client api):
50-
* {@code support.customClient("http://localhost:8080/some/path").get("/some/resource")}.
49+
* {@code support.externalClient("http://localhost:8080/some/path").get("/some/resource")}.
5150
* <p>
5251
* There is a special class-based client constructor {@link #restClient(Class)} for resources. This makes tests
5352
* type-safe as the target resource path is obtained directly from the class annotation. Also, such clients
@@ -274,7 +273,7 @@ public String basePathRest() {
274273
* @param args variables for path placeholders (String.format() arguments)
275274
* @return jersey web target object
276275
* @see #basePathRoot()
277-
* @see #customClient(String, Object...) for external api client
276+
* @see #externalClient(String, Object...) for external api client
278277
*/
279278
@Override
280279
public WebTarget target(final String path, final Object... args) {
@@ -381,9 +380,9 @@ public WebTarget targetRest(final String path, final Object... args) {
381380
*
382381
* @return rest client
383382
*/
384-
public TestRestClient<?> restClient() {
383+
public TestClient<?> restClient() {
385384
// client INHERITS support defaults
386-
return new TestRestClient<>(() -> getClient().target(basePathRest()), defaults);
385+
return new TestClient<>(() -> getClient().target(basePathRest()), defaults);
387386
}
388387

389388
/**
@@ -400,6 +399,7 @@ public TestRestClient<?> restClient() {
400399
* @param <K> resource type
401400
* @return rest client for the given resource class
402401
*/
402+
@Override
403403
public <K> ResourceClient<K> restClient(final Class<K> resource) {
404404
final String target = UriBuilder.newInstance().path(resource).toTemplate();
405405
// client INHERITS support defaults
@@ -439,39 +439,20 @@ public TestClient<?> adminClient() {
439439
/**
440440
* Construct a client for external url.
441441
* <p>
442-
* Example of variables usage: {@code customClient("http://localhost:8080/%s/other", 12)}.
442+
* Example of variables usage: {@code externalClient("http://localhost:8080/%s/other", 12)}.
443443
* <p>
444444
* Will not inherit current defaults.
445445
*
446446
* @param url external url, started with "http(s)" (could contain String.format() placeholders: %s)
447447
* @param args variables for path placeholders (String.format() arguments)
448448
* @return client for the given external url
449449
*/
450-
public TestClient<?> customClient(final String url, final Object... args) {
450+
public TestClient<?> externalClient(final String url, final Object... args) {
451451
checkHttp(url);
452452
// custom external client - no defaults inherited
453453
return new TestClient<>(() -> getClient().target(String.format(url, args)), null);
454454
}
455455

456-
/**
457-
* Construct a rest client for external url. If you have a resource class for external api, then construct
458-
* client with base url first and then apply resource class:
459-
* {@code customRestClient("http://localhost:8080/api/").subClient(ResourceClass.class)}.
460-
* <p>
461-
* Example of variables usage: {@code customRestClient("http://localhost:8080/api/%s/other", 12)}
462-
* <p>
463-
* Will not inherit current defaults.
464-
*
465-
* @param url external url, started with "http(s)" (could contain String.format() placeholders: %s)
466-
* @param args variables for path placeholders (String.format() arguments)
467-
* @return rest client for the given external url
468-
*/
469-
public TestRestClient<?> customRestClient(final String url, final Object... args) {
470-
checkHttp(url);
471-
// custom external client - no defaults inherited
472-
return new TestRestClient<>(() -> getClient().target(String.format(url, args)), null);
473-
}
474-
475456
@Override
476457
public void close() throws Exception {
477458
synchronized (this) {

dropwizard-guicey/src/main/java/ru/vyarus/dropwizard/guice/test/client/ResourceClient.java

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
* @author Vyacheslav Rusakov
3434
* @since 18.09.2025
3535
*/
36-
public class ResourceClient<T> extends TestRestClient<TestRestClient<?>> {
36+
public class ResourceClient<T> extends TestClient<TestClient<?>> {
3737

3838
private final Class<T> resource;
3939

@@ -55,13 +55,9 @@ public ResourceClient(final @Nullable Supplier<WebTarget> root,
5555
* The same as {@link #method(ru.vyarus.dropwizard.guice.url.util.Caller, Object)}, but provides a helper
5656
* utility to easily stub multipart parameters (so these values could be used for request configuration).
5757
* <p>
58-
* For the most common case {@code post(@FormDataParam("file") InputStream stream,
59-
*
60-
* @param consumer consumer calling resource method
61-
* @param caller multipart method caller
62-
* @return pre-configured request builder instance
63-
* @return builder instance for chained calls
64-
* @FormDataParam("file") FormDataContentDisposition fileDetail)}:
58+
* For the most common case
59+
* {@code post(@FormDataParam("file") InputStream stream, @FormDataParam("file")
60+
* FormDataContentDisposition fileDetail)}:
6561
* <pre>{@code multipartMethod((instance, multipart) -> instance
6662
* .post(multipart.fromClasspath("/some.txt"),
6763
* multipart.disposition("file", "some.txt"))}</pre>.
@@ -221,7 +217,7 @@ public TestClientRequestBuilder method(final String method, final @Nullable Obje
221217
}
222218

223219
@Override
224-
public <R> ResourceClient<R> subClient(final Class<R> resource) {
220+
public <R> ResourceClient<R> restClient(final Class<R> resource) {
225221
// to minimize silly mistakes
226222
throw new UnsupportedOperationException("In context of resource, sub-resource client should be obtained "
227223
+ "with subResourceClient() method which ignores sub-resource @Path annotation (not used in "

dropwizard-guicey/src/main/java/ru/vyarus/dropwizard/guice/test/client/TestClient.java

Lines changed: 62 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import ru.vyarus.dropwizard.guice.test.client.builder.TestClientDefaults;
1313
import ru.vyarus.dropwizard.guice.test.client.builder.TestClientRequestBuilder;
1414
import ru.vyarus.dropwizard.guice.test.client.builder.TestRequestConfig;
15+
import ru.vyarus.dropwizard.guice.url.util.RestPathUtils;
1516

1617
import java.net.URI;
1718
import java.util.function.Consumer;
@@ -120,8 +121,7 @@
120121
* </code></pre>
121122
* <p>
122123
* {@link TestClient} is a general client class, but there are special client classes for rest (extending it):
123-
* {@link ru.vyarus.dropwizard.guice.test.client.TestRestClient} and
124-
* {@link ru.vyarus.dropwizard.guice.test.client.ResourceClient} (they could be obtained from the root
124+
* {@link ru.vyarus.dropwizard.guice.test.client.ResourceClient} (could be obtained from the root) and
125125
* {@link ru.vyarus.dropwizard.guice.test.ClientSupport object (which is also a test client)})
126126
*
127127
* @param <T> actual client type
@@ -219,12 +219,12 @@ public Invocation.Builder request(final String path, final Object... args) {
219219
* @param args variables for path placeholders (String.format() arguments)
220220
* @return new client with a different root path
221221
*/
222-
public T subClient(final String path, final Object... args) {
222+
public TestClient<?> subClient(final String path, final Object... args) {
223223
Preconditions.checkState(!path.toLowerCase().startsWith("http"),
224224
"Only sub urls relative to current client url could be used. For completely custom external "
225-
+ "client creation use ClientSupport.customClient()");
225+
+ "client creation use ClientSupport.externalClient()");
226226
// client INHERITS current defaults
227-
return createClient(String.format(path, args));
227+
return new TestClient<>(() -> target(String.format(path, args)), defaults);
228228
}
229229

230230
/**
@@ -240,10 +240,10 @@ public T subClient(final String path, final Object... args) {
240240
* @param consumer uri builder configurator
241241
* @return client with a constructed path (relative to the current client path)
242242
*/
243-
public T subClient(final Consumer<UriBuilder> consumer) {
243+
public TestClient<?> subClient(final Consumer<UriBuilder> consumer) {
244244
final UriBuilder uriBuilder = UriBuilder.newInstance();
245245
consumer.accept(uriBuilder);
246-
return createClient(uriBuilder.toString());
246+
return new TestClient<>(() -> target(uriBuilder.toString()), defaults);
247247
}
248248

249249
/**
@@ -260,6 +260,56 @@ public <K> ResourceClient<K> subClient(final Consumer<UriBuilder> consumer, fina
260260
return new ResourceClient<>(() -> target(uriBuilder.toString()), defaults, resource);
261261
}
262262

263+
/**
264+
* Create a new sub-client for a specified resource class (appends a resource path, obtained from
265+
* {@link jakarta.ws.rs.Path} annotation, to the current client path). Method is useful when generic
266+
* rest path must be "typed" with a resource type (to be able to call resource methods directly).
267+
* <p>
268+
* In case of sub-resources, use {@link #subResourceClient(String, Class, Object...)} to properly specify
269+
* sub-resource mapping path (from lookup method):
270+
* {@code ResourceClient rest = client.subResourceClient("path", SubResource.class)}.
271+
* IMPORTANT: this is NOT THE SAME: {@code client.subClient("path").restClient(SubResource.class)} because
272+
* "restClient()" call would append path from resource, which is ignored for sub resources!.
273+
* <p>
274+
* Defaults could be used to declare path parameter values:
275+
* {@code ResourceClient rest = client.restClient(Resource.class).defaultPathParam("param", "value")} where
276+
* a resource class path is like "/some/{param}/path". With the default path param, there would be no need to
277+
* declare it for each request call.
278+
* <p>
279+
* All defaults, configured for the current client, will be inherited in a sub-client. If this is not required,
280+
* just clean defaults after creation: {@code client.subClient(ResClass.class).reset()}.
281+
*
282+
* @param resource resource class one to build a path for
283+
* @return resource client (with a resource path, relative to the current client path)
284+
* @param <R> resource type
285+
*/
286+
public <R> ResourceClient<R> restClient(final Class<R> resource) {
287+
final String target = RestPathUtils.getResourcePath(resource);
288+
// last class used for a resource type to get methods on
289+
return new ResourceClient<>(() -> target(target), defaults, resource);
290+
}
291+
292+
/**
293+
* Create a sub client for the sub-resource.
294+
* <p>
295+
* IMPORTANT: Path, declared on sub-resource class is ignored! Only lookup method path is counted.
296+
* For example, {@code @Path("/sub") SubResource something() {...}} means all sub resource methods would be
297+
* available on "/sub/*".
298+
*
299+
* @param path sub-resource mapping path (from sub-resource method; could contain String.format()
300+
* placeholders: %s)
301+
* @param args variables for path placeholders (String.format() arguments)
302+
* @param subResource sub-resource
303+
* @param <R> sub-resource type
304+
* @return sub-resource client
305+
*/
306+
public <R> ResourceClient<R> subResourceClient(final String path, final Class<R> subResource,
307+
final Object... args) {
308+
final String target = String.format(path, args);
309+
// last class used for a resource type to get methods on
310+
return new ResourceClient<>(() -> target(target), defaults, subResource);
311+
}
312+
263313
/**
264314
* Cast current path as provided resource (full!) path. Use-case: resources were mapped on non-standard path
265315
* (admin context resources or internal resource mappings).
@@ -268,10 +318,10 @@ public <K> ResourceClient<K> subClient(final Consumer<UriBuilder> consumer, fina
268318
* already a resource path.
269319
*
270320
* @param resource resource type
271-
* @param <T> resource type
321+
* @param <R> resource type
272322
* @return rest client for provided resource
273323
*/
274-
public <T> ResourceClient<T> asRestClient(final Class<T> resource) {
324+
public <R> ResourceClient<R> asRestClient(final Class<R> resource) {
275325
return new ResourceClient<>(() -> target("/"), defaults, resource);
276326
}
277327

@@ -910,12 +960,14 @@ public TestClientRequestBuilder buildDelete(final String path, final Object... a
910960
* <p>
911961
* See {@link ru.vyarus.dropwizard.guice.test.client.builder.FormBuilder#param(String, Object)} for more details
912962
* about parameter values conversion.
963+
* <p>
964+
* Use null path to build entity: {@code buildForm(null).param().buildEntity()}.
913965
*
914966
* @param path target path, relative to rest root (could contain String.format() placeholders: %s)
915967
* @param args variables for path placeholders (String.format() arguments)
916968
* @return form builder
917969
*/
918-
public FormBuilder buildForm(final String path, final Object... args) {
970+
public FormBuilder buildForm(final @Nullable String path, final Object... args) {
919971
return new FormBuilder(target(path, args), defaults);
920972
}
921973

@@ -965,16 +1017,4 @@ private <R> R handleShortcut(final TestClientRequestBuilder request, final @Null
9651017
// immediate result mapping with bypassing exceptions in rest stubs mode (if not exception mapper registered)
9661018
return request.as(result);
9671019
}
968-
969-
/**
970-
* Create a client instance. It is assumed that all underlying classes would override this method to produce
971-
* sub clients of the same type.
972-
*
973-
* @param target target path
974-
* @return client insatnce
975-
*/
976-
@SuppressWarnings("unchecked")
977-
protected T createClient(final String target) {
978-
return (T) new TestClient<>(() -> target(target), defaults);
979-
}
9801020
}

0 commit comments

Comments
 (0)