Hot questions for Using Mockito in jersey client

Top 10 Java Open Source / Mockito / jersey client

Question:

This is my code

@Service
public class PaymentHandler {
private static final Gson GSON = new Gson();

private static Client webServiceClient = createSslClient(); // function creates a ssl connection

public Response makePayment(String payload) {
    WebResource webResource = webServiceClient.resource(url);

    WebResource.Builder builder = webResource.getRequestBuilder();

    String r = builder
            .type(MediaType.APPLICATION_JSON_TYPE)
            .accept(MediaType.APPLICATION_JSON_TYPE)
            .post(String.class, payload);

    Response response = GSON.fromJson(r, Response.class);
}
}

Here is how I try to test it which doesn't work , it always makes a call to the payment service. I am unable to mock it.

Client client = mock(Client.class );
WebResource webResource = mock(WebResource.class);
WebResource.Builder builder = mock(WebResource.Builder.class);
ClientResponse clientResponse = mock(ClientResponse.class);
when(client.resource(anyString())).thenReturn(webResource);
when(webResource.getRequestBuilder()).thenReturn(builder);

when(builder.type(anyString())).thenReturn(builder);
when(builder.accept(anyString())).thenReturn(builder);
when(builder.post(Matchers.eq(String.class), anyString())).thenReturn("Test");
paymentHandler.makePayment(payload); //assume that I send actual payload

Can someone please tell me how to mock this ?


Answer:

In your test, I don't see that you replace the webServiceClient with mocked version.

But first of all, I believe you are better off not writing such code as PaymentHandler without dependency injection. It could be just a simple composition with webServiceClient being injected into the PaymentHandler. Without dependency injection it's not flexible, hardly maintainable and as a result not testable. Imagine, for example, what would happen if initialization of such a field required some interaction with external system. How would you test it without any byte-code-manipultaing libraries? Or how would you easily migrate from one webServiceClient to another, e.g. from non-ssl to ssl?

Despite these well-known problems, sometimes we have to deal with 3rd-party or legacy code which we can't easily change. But we want to write tests for the code which interacts with that 3rd-party code. For this exact reason, there exist some cool test frameworks. PowerMock is one of them and below is the working code using that it:

@RunWith(PowerMockRunner.class)
@PrepareForTest(PaymentHandler.class)
public class PaymentHandlerTest {
    @Test
    public void test() throws Exception {
        //we don't want to initialize the PaymentHandler.class because it might cause some
        //heavy undesirable initilization. E.g. if we had referred to PaymentHandler as a class
        //literal here, then the webServiceClient would've been initializaed with some "real"
        //instance of Client. My PaymentHandler is located in so package. You should specify your
        //fully qualified class' name here
        Class<?> clazz = Thread.currentThread().getContextClassLoader().loadClass("so.PaymentHandler");

        //now the webServiceClient will be null once we initialize the PaymentHandler class
        PowerMockito.suppress(PowerMockito.method(clazz, "createSslClient"));
        Client client = mock(Client.class);

        //here we initialize the PaymentHandler.class and finally mock the webServiceClient
        Whitebox.setInternalState(clazz, "webServiceClient", client);

        PaymentHandler paymentHandler = new PaymentHandler();

        WebResource webResource = mock(WebResource.class);
        WebResource.Builder builder = mock(WebResource.Builder.class);
        when(client.resource(anyString())).thenReturn(webResource);
        when(webResource.getRequestBuilder()).thenReturn(builder);
        //take note of any(MediaType.class) instead of anyString() from your example. As in
        //your PaymentHandler, MediaType is used instead of String
        when(builder.type(any(MediaType.class))).thenReturn(builder);
        when(builder.accept(any(MediaType.class))).thenReturn(builder);
        when(builder.post(Matchers.eq(String.class), anyString())).thenReturn("{}");

        paymentHandler.makePayment("payload");
    }
}

In my example, I used the following dependencies:

testCompile group: 'org.powermock', name: 'powermock-api-mockito2', version: '2.0.0'
testCompile group: 'org.powermock', name: 'powermock-module-junit4', version: '2.0.0'

These are the latest versions but earlier versions can do it as well