3

I am using the MicroProfile REST client in Quarkus and would like to know how I can unit test custom client interfaces?

Example service:

@Path("/v1")
@RegisterRestClient
public interface CustomService {

    @POST
    @Path("/custom")
    void postCustomObject(CustomObject object);
}

Is it possible to write a unit test that covers this functionality? E.g. I would like to test that the request body is processed properly and contains the correct JSON (especially since I have an issue where the behaviour differs between JVM and native image mode).

REST server resources can easily be tested with REST-assured, but I did not find anything similar for REST client interfaces.

The Quarkus guide on using the REST client does not help me any further either, since it is using an actual service to make the call. In my case the server side is not available during the build/test process.

Any suggestions?

dawis11
  • 820
  • 1
  • 9
  • 24
  • Actually, what you are trying to test is the MicroProfile REST client itself. You shouldn't. It works property. It has been already tested. You can test instead the layer just before call `client.postCustomObject`, so you verify that client's method is called with the CustomObject you expect. – Héctor Apr 28 '20 at 15:19
  • I would tend to agree with you, @Héctor. But I'm experiencing issues at runtime when using native image mode. And I would like to be able to detect this up front if possible. – Pieterjan Deconinck Apr 28 '20 at 15:23
  • 1
    I understand. In that case, you could use some request interceptor to check (manually I guess) how the actual request is. – Héctor Apr 28 '20 at 15:27
  • You can use wiremock to mock the server, it is easy to use and you can make verification on it to validate that a request has been send. – loicmathieu Apr 29 '20 at 08:03

1 Answers1

1

A little bit late, but I try to answer the question for future reference. Although I agree with the @Héctor's comment, it could be useful to test the MicroProfile rest client itself (e.g. to actually test an error handler) or in the bean which the rest client is injected in.

Anyway, you need a mock server such as WireMock. The official guide for Quarkus actually covers this topic. I can report here an example for it (which I took from here).

Add this dependency to your pom.xml (if you are using Maven):

<dependency>
    <groupId>com.github.tomakehurst</groupId>
    <artifactId>wiremock-jre8</artifactId>
    <scope>test</scope>
    <version>${wiremock.version}</version> 
</dependency>

Create a class which will start the mock server befor the tests are executed (and which will shutdown it once they are all executed):

package org.acme.getting.started.country;

import java.util.Collections;
import java.util.Map;

import com.github.tomakehurst.wiremock.WireMockServer;
import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;

import static com.github.tomakehurst.wiremock.client.WireMock.*;

public class WiremockCountries implements QuarkusTestResourceLifecycleManager {

    private WireMockServer wireMockServer;

    @Override
    public Map<String, String> start() {

        wireMockServer = new WireMockServer();
        wireMockServer.start();

        //define a static response when the request matches a url declared as a regex 
        stubFor(get(urlEqualTo("/v2/name/GR"))
                .willReturn(aResponse()
                        .withHeader("Content-Type", "application/json")
                        //read the WireMock docs: you can even use a json file in /resources/__files/ (default expected location) in place of a string
                        .withBody(
                        """
                        [
                            {
                                "name": "Ελλάδα",
                                "capital": "Αθήνα"
                            }
                        ]
                        """
                        )));
        //remap the base url of the external service to the base url of the mock server 
        return Collections.singletonMap("org.acme.getting.started.country.CountriesService/mp-rest/url", wireMockServer.baseUrl());
    }

    @Override
    public void stop() {
        if (null != wireMockServer) {
            wireMockServer.stop();
        }
    }
}

Memento: at the moment, you cannot test a @Singleton bean, so put on the rest client interface the annotation @ApplicationScoped.

Finally, use this class in the actual test class:

package org.acme.getting.started.country;

import javax.inject.Inject;

import io.quarkus.test.common.QuarkusTestResource;
import io.quarkus.test.junit.QuarkusTest;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

@QuarkusTest
@QuarkusTestResource(WiremockCountries.class)
class RegularCountriesServiceTest {

    @Inject
    @RestClient
    CountriesService countriesService;

    @Test
    void testGR() {
//      assertThat(countriesService.getByName("GR")).hasSize(10).extracting("name").contains("Greece");
        assertThat(countriesService.getByName("GR")).hasSize(1).extracting("name").contains("Ελλάδα");
    }
}

If you want to test the rest client one level above, all you need to do is reusing the wrapper class for the mock server and inject with @Inject the bean which declares the rest client as its dependency. Do something like this:

@QuarkusTest
@QuarkusTestResource(WiremockWrapperAsBefore.class)
class MyBusinessClassTest {

    @Inject
    MyBusinessClass myBusinessClass;

    @Test
    void testMyRestCleintInjectedIntoMyBusinessClass() {

        ResponseDTO dto = myBusinessClass.methodWhichCallsMyMicroProfileRestClient(String someParam);
        assertNotNull(dto);
    }
}

Otherwise, it could be useful in another scenarios to create a proxy implementation with RestClientBuilder for the MicroProfile rest client interface, as shown here:

import java.net.MalformedURLException;
import java.net.URI;
import org.eclipse.microprofile.rest.client.RestClientBuilder;
import static org.junit.Assert.assertNotNull;
import org.junit.Test;


public class RestClientTest {

    @Test
    public void init() throws MalformedURLException {
        URI baseURI = URI.create("http://localhost:8080");
        PingClient client = RestClientBuilder.newBuilder().
                baseUri(baseURI).
                build(PingClient.class);                
        assertNotNull(client);
        String result = client.ping();
        assertNotNull(result);
    }
}

The corresponding interface is easy to guess, but it is like follows:

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
        
@Path("/restclient/resources/ping")
@RegisterRestClient
public interface PingClient {

    @GET
    String ping();
}

You won't need the following dependencies in that case if you are using Quarkus, but I will report the anyway from the link above in case someone is using MicroProfile but not Quarkus:

<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>javax</groupId>
        <artifactId>javaee-api</artifactId>
        <version>8.0</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>org.apache.cxf</groupId>
        <artifactId>cxf-rt-rs-mp-client</artifactId>
        <version>3.3.1</version>
        <scope>test</scope>
    </dependency>
</dependencies>  
giulianopz
  • 184
  • 1
  • 8