Hot questions for Using Mockito in 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