Socket
Book a DemoInstallSign in
Socket

io.github.sola-ris:jax-rs-client-test

Package Overview
Dependencies
Maintainers
1
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

io.github.sola-ris:jax-rs-client-test

A library for testing classes that rely on a JAX-RS Client without starting a full server or relying on mocking libraries.

Source
mavenMaven
Version
0.2.0
Version published
Maintainers
1
Source

JAX-RS Client Test

Maven Central Version javadoc codecov

A library for testing classes that use a JAX-RS Client without starting a full server or relying on mocking libraries.

Heavily inspired by Spring's Client testing infrastructure.

Compatibility

  • JDK 17 or higher
  • JAX-RS (Jakarta RESTful Web Services) 3.1

Maven coordinates

<dependency>
    <groupId>io.github.sola-ris</groupId>
    <artifactId>jax-rs-client-test</artifactId>
    <version>0.1.0</version>
</dependency>

Usage Guide

Basic Usage

The idea is to declare the expected requests and provide "mock" or "stub" responses so the code can be tested in isolation, i.e. without starting a server.

The following example shows how to do so with a Client:

Client client = ClientBuilder.newClient();

MockRestServer server = MockRestServer.bindTo(client).build();
server.expect(RequestMatchers.requestTo("/users/42"))
        .andRespond(MockResponseCreators.withNotFound());

// Test code that uses the Client

server.verify();

In the example, the MockRestServer (the entrypoint into the library) configures the Client with a ClientRequestFilter that matches the incoming request against the set-up expectations and returns the "stub" responses. In this case, we expect a call to /users/42 and want to return a response with status code 404. Additional expected requests and stub responses can be defined as needed. At the end of the test, server.verify() can be used to verify that all the expected requests were indeed executed.

Repeated requests

Unless specified otherwise, each expected request is expected to be executed exactly once. The expect method of the MockRestServer provides an overload that accepts an ExpectedCount argument that specifies a range count (e.g. once, between, min, max, etc.) for the given request expectation.

The following example shows how to do so with a WebTarget:

WebTarget target = ClientBuilder.newClient().target("");

MockRestServer server = MockRestServer.bindTo(target).build();
server.expect(ExpectedCount.max(3), RequestMatchers.requestTo("/hello"))
        .andRespond(MockResponseCreators.withSuccess());
server.expect(ExpectedCount.between(1, 2), RequestMatchers.requestTo("/goodbye"))
        .andRespond(MockResponseCreators.withSuccess());

// Use the bound WebTarget e.g. by passing it to the class under test

server.verify();

Request ordering

By default, only the first invocation of each expected request is expected to occur in order of declaration. This behavior can be changed by passing the desired RequestOrder to withRequestOrder when building the server.

ClientBuilder clientBuilder = ClientBuilder.newBuilder()

MockRestServer server = MockRestServer.bindTo(clientBuilder)
        .withRequestOrder(RequestOrder.UNORDERED)
        .build();

The following options are available:

  • ORDERED
    • Expect the first invocation of each request in order of declaration. Subsequent requests may occur in any order. This is the default.
  • UNORDERED
    • Expect all invocations in any order.
  • STRICT
    • Expect the minimum amount of expected requests to occur in order of declaration. Subsequent requests may occur in any order.

Mixing stubs and real responses

In some tests it may be necessary to mock only some of the requests and call an actual remote service or others. This can be done through an ExecutingResponseCreator:

Client client = ClientBuilder.newClient();

MockRestServer server = MockRestServer.bindTo(client).build();

server.expect(RequestMatchers.requestTo("/profile/42"))
        .andRespond(new ExecutingResponseCreator()); // <1>

Client customClient = ClientBuilder.newBuilder().register(new MyAuthFilter()).build(); // <2>

server.expect(RequestMatchers.requestTo("/users/42")) // <3>
        .andExpect(RequestMatchers.method("GET"))
        .andRespond(new ExecutingResponseCreator(customClient));

server.expect(RequestMatchers.requestTo("/users/42")) // <4>
        .andExpect(RequestMatchers.method("DELETE"))
        .andRespond(MockResponseCreators.withSuccess());

// Test code that uses the Client

customClient.close(); // <5>

server.verify();
  • Respond to /profile/42 by calling the actual service with a default Client
  • Create a custom Client with e.g. custom authentication
  • Respond to GET /users/42 by calling the actual service through customClient
  • Respond to DELETE /users/42 with a stub
  • Clients passed to an ExecutingResponseCreator must be closed by the caller

Request matchers

JAX-RS Client Test comes with a number of built-in RequestMacher implementations, all accessed via factory methods in RequestMatchers.

  • RequestMatchers
    • Matchers for basic request attributes like the URI or HTTP method, access to all other RequestMatcher implementations
  • EntityRequestMatchers
    • Matchers related to the request entity / body
  • JsonPathRequestMatchers
    • Matchers that evaluate a JsonPath expression against the request body
  • XpathRequestMatchers
    • Matchers that evaluate an XPath expression against the request body

Custom request matchers can be implemented as a lambda and can access the current ClientRequestContext. All implementations must throw an AssertionError if the incoming request does not match and return if it does.

For example:

RequestMatcher myMatcher = request -> {
    if (Locale.ENGLISH.equals(request.getLanguage())) {
        throw new AssertionError("Expected a language other than ENGLISH.")
    }
};

Matching the request entity

When implementing a RequestMatcher, the entity can be accessed via ClientRequestContext#getEntity. If the matcher requires a different representation of the entity, e.g. a JSON String instead of POJO, it can be converted via an EntityConverter, which can be obtained by calling EntityConverter#fromRequestContext. The EntityConverter has the same capabilities as the client that made the request, so if the client has a provider that can handle POJO -> JSON String conversion, the EntityConverter can do it as well.

For example:

RequestMatcher myMatcher = request -> {
    EntityConverter converter = EntityConverter.fromRequestContext(request);
    
    // Assuming the MediaType is application/json
    String jsonString = converter.convertEntity(request, String.class);
    
    // Assertions on the string
};

Matching multipart/form-data (EntityPart)

EntityPart has certain limitations that require it to be handled separately from other request entities:

  • It is InputStream based, meaning its contents can only be accessed once
  • Implementations are not required to override equals, preventing comparison with another EntityPart

The EntityConverter provides two methods to work around this:

  • bufferExpectedMultipart, which takes an arbitrary List<EntityPart> and buffers it
  • bufferMultipartRequest, which takes the List<EntityPart> from the current ClientRequestContext, buffers it and sets the request up for further processing or repeated buffering in another RequestMatcher

For example:

EntityPart myEntityPart = EntityPart.withName("username")
        .mediaType(MediaType.TEXT_PLAIN_TYPE)
        .content("admin")
        .build();

RequestMatcher myMultipartMatcher = request -> {
    EntityConverter converter = EntityConverter.fromRequestContext(request);
    EntityPart myBufferedPart = converter.bufferExpectedMultipart(List.of(myEntityPart)).get(0);
    // MediaType multipart/form-data and entity of type List<EntityPart> is assumed
    EntityPart bufferedRequestPart = converter.bufferMultipartRequest(request).get(0);

    // Assuming AssertJ is present

    // Reading the content here does not prevent further reads
    // in another matcher or during request execution
    assertThat(myBufferedPart.getContent(String.class))
            .isEqualTo(bufferedRequestPart.getContent(String.class))

    // Buffered EntityParts implement equals()
    assertThat(myBufferedPart)
            .isEqualTo(bufferedRequestPart)
};

// Set up the MockRestServer and execute the request

Tested implementations

Limitations

The library relies on a ClientRequestFilter that calls ClientRequestContext#abortWith in order to create its mock response, making it impossible to test classes that use a client that relies on the same behavior.

FAQs

Package last updated on 13 Sep 2025

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts