Enterprise Java

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.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

2 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Gerard Davison
10 years ago

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

G.H.
G.H.
10 years ago

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.

Back to top button