Jersey Client: Testing external calls

 
Jim and I have been doing a bit of work over the last week which involved calling neo4j’s HA status URI to check whether or not an instance was a master/slave and we’ve been using jersey-client.
 
The code looked roughly like this:
 
 
 
 
 

class Neo4jInstance {
    private Client httpClient;
    private URI hostname;

    public Neo4jInstance(Client httpClient, URI hostname) {
        this.httpClient = httpClient;
        this.hostname = hostname;
    }

    public Boolean isSlave() {
        String slaveURI = hostname.toString() + ":7474/db/manage/server/ha/slave";
        ClientResponse response = httpClient.resource(slaveURI).accept(TEXT_PLAIN).get(ClientResponse.class);
        return Boolean.parseBoolean(response.getEntity(String.class));
    }
}

While writing some tests against this code we wanted to stub out the actual calls to the HA slave URI so we could simulate both conditions and a brief search suggested that mockito was the way to go.

We ended up with a test that looked like this:

@Test
public void shouldIndicateInstanceIsSlave() {
    Client client = mock( Client.class );
    WebResource webResource = mock( WebResource.class );
    WebResource.Builder builder = mock( WebResource.Builder.class );

    ClientResponse clientResponse = mock( ClientResponse.class );
    when( builder.get( ClientResponse.class ) ).thenReturn( clientResponse );
    when( clientResponse.getEntity( String.class ) ).thenReturn( "true" );
    when( webResource.accept( anyString() ) ).thenReturn( builder );
    when( client.resource( anyString() ) ).thenReturn( webResource );

    Boolean isSlave = new Neo4jInstance(client, URI.create("http://localhost")).isSlave();

    assertTrue(isSlave);
}

which is pretty gnarly but does the job.

I thought there must be a better way so I continued searching and eventually came across this post on the mailing list which suggested creating a custom ClientHandler and stubbing out requests/responses there.

I had a go at doing that and wrapped it with a little DSL that only covers our very specific use case:

private static ClientBuilder client() {
    return new ClientBuilder();
}

static class ClientBuilder {
    private String uri;
    private int statusCode;
    private String content;

    public ClientBuilder requestFor(String uri) {
        this.uri = uri;
        return this;
    }

    public ClientBuilder returns(int statusCode) {
        this.statusCode = statusCode;
        return this;
    }

    public Client create() {
        return new Client() {
            public ClientResponse handle(ClientRequest request) throws ClientHandlerException {
                if (request.getURI().toString().equals(uri)) {
                    InBoundHeaders headers = new InBoundHeaders();
                    headers.put("Content-Type", asList("text/plain"));
                    return createDummyResponse(headers);
                }
                throw new RuntimeException("No stub defined for " + request.getURI());
            }
        };
    }

    private ClientResponse createDummyResponse(InBoundHeaders headers) {
        return new ClientResponse(statusCode, headers, new ByteArrayInputStream(content.getBytes()), messageBodyWorkers());
    }

    private MessageBodyWorkers messageBodyWorkers() {
        return new MessageBodyWorkers() {
            public Map<MediaType, List<MessageBodyReader>> getReaders(MediaType mediaType) {
                return null;
            }

            public Map<MediaType, List<MessageBodyWriter>> getWriters(MediaType mediaType) {
                return null;
            }

            public String readersToString(Map<MediaType, List<MessageBodyReader>> mediaTypeListMap) {
                return null;
            }

            public String writersToString(Map<MediaType, List<MessageBodyWriter>> mediaTypeListMap) {
                return null;
            }

            public <T> MessageBodyReader<T> getMessageBodyReader(Class<T> tClass, Type type, Annotation[] annotations, MediaType mediaType) {
                return (MessageBodyReader<T>) new StringProvider();
            }

            public <T> MessageBodyWriter<T> getMessageBodyWriter(Class<T> tClass, Type type, Annotation[] annotations, MediaType mediaType) {
                return null;
            }

            public <T> List<MediaType> getMessageBodyWriterMediaTypes(Class<T> tClass, Type type, Annotation[] annotations) {
                return null;
            }

            public <T> MediaType getMessageBodyWriterMediaType(Class<T> tClass, Type type, Annotation[] annotations, List<MediaType> mediaTypes) {
                return null;
            }
        };
    }

    public ClientBuilder content(String content) {
        this.content = content;
        return this;
    }
}

If we change our test to use this code it now looks like this:

@Test
public void shouldIndicateInstanceIsSlave() {
    Client client = client().requestFor("http://localhost:7474/db/manage/server/ha/slave").
                             returns(200).
                             content("true").
                             create();
    Boolean isSlave = new Neo4jInstance(client, URI.create("http://localhost")).isSlave();
    assertTrue(isSlave);
}

Is there a better way?

In Ruby I’ve used WebMock to achieve this and Ashok pointed me towards WebStub which looks nice except I’d need to pass in the hostname + port rather than constructing that in the code.

 

Reference: Jersey Client: Testing external calls from our JCG partner Mark Needham at the Mark Needham Blog blog.

Do you want to know how to develop your skillset to become a Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you two of our best selling eBooks for FREE!

JPA Mini Book

Learn how to leverage the power of JPA in order to create robust and flexible Java applications. With this Mini Book, you will get introduced to JPA and smoothly transition to more advanced concepts.

JVM Troubleshooting Guide

The Java virtual machine is really the foundation of any Java EE platform. Learn how to master it with this advanced guide!

Given email address is already subscribed, thank you!
Oops. Something went wrong. Please try again later.
Please provide a valid email address.
Thank you, your sign-up request was successful! Please check your e-mail inbox.
Please complete the CAPTCHA.
Please fill in the required fields.

2 Responses to "Jersey Client: Testing external calls"

  1. Hi,

    If you make use of the Jersey class TerminatingClientHandler you can simplify this code quite a bit, here in the test class that I use in the wadl.java.net project:

    https://java.net/projects/wadl/sources/svn/content/trunk/wadl/wadl-maven-plugin/src/test/java/org/jvnet/ws/wadl/maven/Wadl2JavaMojoTest.java?rev=389

    And the same code, obviously slightly different, when using JAX-RS 2.0:

    https://java.net/projects/wadl/sources/svn/content/trunk/wadl/wadl-maven-plugin/src/test/java/org/jvnet/ws/wadl/maven/Wadl2JavaMojoJAXRS20Test.java?rev=389

    Hope this is helpful,

    Gerard

  2. G.H. says:

    The second approach aside: How did you get the first one to even run? When I try mocking WebResource.Builder as you do up there, I get the following:

    org.mockito.exceptions.base.MockitoException:
    Cannot mock/spy class com.sun.jersey.api.client.WebResource$Builder
    Mockito cannot mock/spy following:
    – final classes
    – anonymous classes
    – primitive types

    as WebResource.Builder is declared final.

Leave a Reply


× two = 16



Java Code Geeks and all content copyright © 2010-2014, Exelixis Media Ltd | Terms of Use | Privacy Policy
All trademarks and registered trademarks appearing on Java Code Geeks are the property of their respective owners.
Java is a trademark or registered trademark of Oracle Corporation in the United States and other countries.
Java Code Geeks is not connected to Oracle Corporation and is not sponsored by Oracle Corporation.
Do you want to know how to develop your skillset and become a ...
Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you two of our best selling eBooks for FREE!

Get ready to Rock!
You can download the complementary eBooks using the links below:
Close