Class ResourceClient<T>

Type Parameters:
T - resource type

public class ResourceClient<T> extends TestClient<TestClient<?>>
Specialized rest client for resource classes. Useful to construct target paths directly from resource class methods.

For example: client.method(mock -> mock.restMethod("queryParam").asVoid())) It would resolve target HTTP method, target path (from method annotation) and apply query parameter (because of not null value provided for method parameter, annotated with QueryParam).

This simplifies test request building, making it almost refactoring-safely. Also, it makes simple navigation from test to called resource method.

It is still possible to call methods by path with generic methods like TestClient.get(String, Class, Object...) or TestClient.buildGet(String, Object...).

Wrapper for JerseyClient. Jersey client is a general-purpose client and this api specialized for tests. Also, the jersey request builder is split into two parts: target and request. This api unifies it into a single step, for simplicity.

The "defaults" concept: some values (query params, headers, cookies, etc.) could be configured once for all client requests (this is usually handy for tests, for example, to unify authorization). See "default*" methods for details (for example, TestClientDefaults.defaultHeader(String, Object)). Defaults could be cleared with TestClientDefaults.reset() or printed into console with TestClientDefaults.printDefaults()

There are multiple ways to perform a request:

All "build*" methods return a custom builder object with all the same configuration options as the original jersey client builder, but all gathered in one place (easier to use). This builder could return either directly mapped object (with TestClientRequestBuilder.as(Class)) or a special response object wrapper with pre-defined assertions to simplify testing (TestClientRequestBuilder.expectSuccess(Integer...) or TestClientRequestBuilder.expectFailure(Integer...) or without response validation TestClientRequestBuilder.invoke()).

All methods support (optional) string format for a path. For example, the call client.get("/entity/%s", Entity.class, 12) will use "/entity/12/" as target path.

Examples:


     TestClient client;

     // jersey api (no defaults applied)
     Entity res = client.target("/some/path").request().get(Entity.class);

     // jersey api (with defaults applied)
     Entity res = client.request("/some/path").get(Entity.class);

     // shortcut method
     Entity res = client.get("/some/path", Entity.class);

     // shortcut with composite entity type (note diamond operator usage):
     List<Entity> res = client.get("/some/path", new GenericType<>() {});

     // builder method
     Entity res = client.buildGet("/some/path")
              .header("Something", "Value")
              .invoke(Entity.class);

     // builder with response assertions ():
     Entity res = client.buildGet("/some/path")
              // error thrown if response is not success with (optional) exact status validation
              .expectSuccess(200)
              // assertion error if value will not match or cookie absent
              .assertCookie("Name", "Value")
              // assertion error if value will not match or header absent
              .assertHeader("Something", "Value")
              // map entity
              .as(EntityClass)

     // defaults demo:
     client.defaultHeader("Authorization", "Bearer 1234567890");

     // request would contain the default header
     Entity res = client.get("/some/path", Entity.class);
 

The main idea behind chained assertions is redundant variables avoidance in test.

Note that builder method return AutoCloseable object (same as Response) because a response object must be closed (response is closed when response body is consumed). Because of this your IDE could warn you that the result of, for example, TestClientRequestBuilder.invoke() must be used with "try-with-resources" statement. In most cases, you can ignore this warning as guicey tracks all such wrapped responses and would close it after a test application shutdown.

Builder api does not hide jersey api: if required, you can modify target directly with TestClientDefaults.defaultPathConfiguration(java.util.function.Function) or jersey builder with TestClientDefaults.defaultRequestConfiguration(java.util.function.Consumer).

Builder supports jersey client properties (TestClientDefaults.defaultProperty(String, Object)) and extensions TestClientDefaults.defaultRegister(Class) and TestClientDefaults.defaultRegister(Object). As an example, property could be used to disable redirects (TestClientRequestBuilder.notFollowRedirects() which is used automatically by TestClientRequestBuilder.expectRedirect(Integer...)). For a custom extension example see TestClientRequestBuilder.noBodyMappingForVoid() (which is used automatically by TestClientRequestBuilder.asVoid() to ignore response body mapping for void requests).

The client could be used to build sub-clients. In this case all defaults will be inherited from the parent client. For example, we have a general rest client, but our particular test would check only one resource. In this case we can create a sub-client for the resource path to avoid it in all method calls:


     TestClient api;
     TestClient subRest = api.subClient("/resource/{param}/path")
              .defaultPathParam("param", 123);
      // this would call "/resource/123/path/method"
     Entity res = subRest.get("/method", Entity.class);
 

TestClient is a general client class, but there are special client classes for rest (extending it): ResourceClient (could be obtained from the root) and object (which is also a test client))

Since:
18.09.2025
  • Constructor Details

    • ResourceClient

      public ResourceClient(@Nullable Supplier<jakarta.ws.rs.client.WebTarget> root, @Nullable TestRequestConfig defaults, Class<T> resource)
      Create a resource client.
      Parameters:
      root - root path
      defaults - defaults
      resource - resource type
  • Method Details

    • multipartMethod

      public TestClientRequestBuilder multipartMethod(MultipartAwareCaller<T> caller)
      The same as method(ru.vyarus.dropwizard.guice.url.util.Caller, Object), but provides a helper utility to easily stub multipart parameters (so these values could be used for request configuration).

      For the most common case post(@FormDataParam("file") InputStream stream, @FormDataParam("file") FormDataContentDisposition fileDetail):

      multipartMethod((instance, multipart) -> instance
                   .post(multipart.fromClasspath("/some.txt"),
                         multipart.disposition("file", "some.txt"))
      .

      it could be a single field: post(@FormDataParam("file") FormDataBodyPart file):

      multipartMethod((instance, multipart) -> instance
                   .post(multipart.filePart("/some.txt"))
      or to get file from classpath:
      multipartMethod((instance, multipart) -> instance
                   .post(multipart.streamPart("/some.txt"))
      .

      There might be multiple files for the same field post(@FormDataParam("file") List<FormDataBodyPart> file):

      multipartMethod((instance, multipart) -> instance
                .post(Arrays.asList(
                    multipart.filePart("/some.txt"),
                    multipart.filePart("/other.txt")))
      .

      When the method only accepts content-disposition mapping, it would be also used with en empty file content (as file content is not required, preserve available data): post(@FormDataParam("file") FormDataContentDisposition file)

      multipartMethod((instance, multipart) -> instance
                 .post(multipart.disposition("file", "some.txt"))
      .

      The method parameter could be a complete multipart object (with all fields inside): post(FormDataMultiPart multiPart): for such cases there is a special builder:

      multipartMethod((instance, multipart) -> instance
                 .post(multipart.multipart()
                        .field("foo", "val")
                        .file("file1", "/some.txt)
                        .stream("file2", "/other.txt)
                        .build())

      It is not required to specify all parameters: you may use null on any of them (it is just a one way to configure request).

      Note that it is not required to declare fields like this! You can always prepare entity manually and use caller with body method(ru.vyarus.dropwizard.guice.url.util.Caller, Object).

      Parameters:
      caller - multipart method caller
      Returns:
      builder instance for chained calls
    • method

      public TestClientRequestBuilder method(Caller<T> caller)
      Configure request from resource method call. See method(ru.vyarus.dropwizard.guice.url.util.Caller, Object).
      Parameters:
      caller - resource method caller
      Returns:
      pre-configured request builder instance
    • method

      public TestClientRequestBuilder method(Caller<T> caller, @Nullable Object body)
      Configure a request from the resource method call. The main idea here: request could be configured automatically from the resource method annotations. Arguments for annotated parameters could be used to provide default values.
      • target path resolved from method Path
      • target method resolved from method annotation (like GET)
      • analyze provided method arguments to get values for annotated parameters (like QueryParam, PathParam, etc.)
      • build form body from not null FormParam and FormDataParam

      All these configuration values could be overridden with a received builder. Also, put null on arguments that should not be configured automatically.

      Note that by default requests are logged, so the correctness of the target url could be verified easily.

      Might also include sub resource call, if sub resource locator method returns resource instance: resource.sub(args).method(args)

      Parameters:
      caller - resource method caller
      body - request body (form param ignored for POST if body provided)
      Returns:
      pre-configured request builder instance
    • method

      public TestClientRequestBuilder method(String method)
      Create request builder from resource method: automatic configuration of targe path and http method selection.

      For more advanced defaults configuration see method(ru.vyarus.dropwizard.guice.url.util.Caller) (it will also apply configuration by provided method parameter values).

      If multiple methods selected, will select method without arguments. If there is no matching no-args method found throws exception (about multiple methods found). This no-args behavior is required to comply with arguments-based search (it would be otherwise impossible to search for no-args method).

      Parameters:
      method - method name to call (there must be only one such method)
      Returns:
      request builder instance
      Throws:
      IllegalStateException - if unique method not found
    • method

      public TestClientRequestBuilder method(Method method)
      Create request builder from resource method: automatic configuration of targe path and http method selection.

      For more advanced defaults configuration see method(ru.vyarus.dropwizard.guice.url.util.Caller) (it will also apply configuration by provided method parameter values).

      Parameters:
      method - method name to call (there must be only one such method)
      Returns:
      request builder instance
    • method

      public TestClientRequestBuilder method(Method method, @Nullable Object body)
      Create request builder from resource method: automatic configuration of targe path and http method selection.

      For more advanced defaults configuration see method(ru.vyarus.dropwizard.guice.url.util.Caller, Object) (it will also apply configuration by provided method parameter values).

      Parameters:
      method - method name to call (there must be only one such method)
      body - (optional) request body (everything except Entity converted to JSON)
      Returns:
      request builder instance
      Throws:
      IllegalStateException - if jersey annotations aren't found on the method
    • method

      public TestClientRequestBuilder method(String method, @Nullable Object body)
      Create request builder from resource method: automatic configuration of targe path and http method selection.

      For more advanced defaults configuration see method(ru.vyarus.dropwizard.guice.url.util.Caller, Object) (it will also apply configuration by provided method parameter values).

      If multiple methods selected, will select the method without arguments. If there is no matching no-args method found, throws exception (about multiple methods found).

      Parameters:
      method - method name to call (there must be only one such method)
      body - (optional) request body (everything except Entity converted to JSON)
      Returns:
      request builder instance
      Throws:
      IllegalStateException - if unique method not found
    • restClient

      public <R> ResourceClient<R> restClient(Class<R> resource)
      Description copied from class: TestClient
      Create a new sub-client for a specified resource class (appends a resource path, obtained from Path annotation, to the current client path). Method is useful when generic rest path must be "typed" with a resource type (to be able to call resource methods directly).

      In case of sub-resources, use TestClient.subResourceClient(String, Class, Object...) to properly specify sub-resource mapping path (from lookup method): ResourceClient rest = client.subResourceClient("path", SubResource.class). IMPORTANT: this is NOT THE SAME: client.subClient("path").restClient(SubResource.class) because "restClient()" call would append path from resource, which is ignored for sub resources!.

      Defaults could be used to declare path parameter values: ResourceClient rest = client.restClient(Resource.class).defaultPathParam("param", "value") where a resource class path is like "/some/{param}/path". With the default path param, there would be no need to declare it for each request call.

      All defaults, configured for the current client, will be inherited in a sub-client. If this is not required, just clean defaults after creation: client.subClient(ResClass.class).reset().

      Overrides:
      restClient in class TestClient<TestClient<?>>
      Type Parameters:
      R - resource type
      Parameters:
      resource - resource class one to build a path for
      Returns:
      resource client (with a resource path, relative to the current client path)
    • subResourceClient

      public <R> ResourceClient<R> subResourceClient(Caller<T> caller, Class<R> subResource)
      Create a sub client for the sub-resource. Sub resource path is resolved through the called locator method. Note: multiple locator methods could be called at once!
      Type Parameters:
      R - sub-resource type
      Parameters:
      caller - locator method(s) caller
      subResource - sub-resource type
      Returns:
      sub-resource client
    • getResourceType

      public Class<T> getResourceType()
      Returns:
      resource type
    • toString

      public String toString()
      Overrides:
      toString in class TestClient<TestClient<?>>