29

I wrote code which calls the Jersey client API which in turn calls a web service which is out of my control. I do not want my unit test to call the actual web service.

What is the best approach for writing a unit test for code which calls the Jersey client API? Should I use the Jersey server API to write a JAX-RS web service and then use the Jersey Test Framework for the unit test? Or should I mock out the Jersey web service calls? I have access to JMock. Or should I try another approach?

During my research, I found this discussion describing various options, but I did find a complete solution. Are there any code examples available showing a suggested JUnit approach? I could not find any in the Jersey documentation.

Here is the relevant source code:

public String getResult(URI uri) throws Exception {
  // error handling code removed for clarity
  ClientConfig clientConfig = new DefaultClientConfig();
  Client client = Client.create(clientConfig);
  WebResource service = client.resource(uri);
  String result = service.accept(accept).get(String.class);
  return result;
}

Here are examples of test code I would like to pass. I would like to test (1) passing in a valid URI and getting a valid string back and (2) passing in an invalid (for whatever reason -- unreachable or unauthorized) URI and getting an exception back.

@Test
public void testGetResult_ValidUri() throws Exception {
  String xml = retriever.getResult(VALID_URI);
  Assert.assertFalse(StringUtils.isBlank(xml));
}

@Test(expected = IllegalArgumentException.class)
public void testGetResult_InvalidUri() throws Exception {
  retriever.getResult(INVALID_URI);
}

Everything above is the simple description of what my code does. In reality, there is a layer on top of that that accepts two URIs, first tries calling the first URI, and if that URI fails then it tries calling the second URI. I would like to have unit tests covering (1) the first URI succeeds, (2) the first URI fails and the second URI succeeds, and (3) both URIs fail. This code is sufficiently complex that I want to test these different scenarios using JUnit, but to do this I either need to run actual stand-in web services or mock out the Jersey client API calls.

BennyMcBenBen
  • 1,438
  • 2
  • 20
  • 37
  • What exactly do you want to test in this method? What are the critereas, that test is passed? – dbf Jun 12 '12 at 21:45
  • Well, If you want to do unit test then you must have the proper functional services. – iDroid Jun 12 '12 at 22:06
  • @dbf I updated the question with that I want to test (1) passing in a valid URI and getting a valid string back and (2) passing in an invalid (for whatever reason -- unreachable or unauthorized) URI and getting an exception back. – BennyMcBenBen Jun 12 '12 at 22:11

3 Answers3

15

Try to use Mockito or Easymock for mocking service calls. You need to mock only these methods which are actually used - no need to mock every method. You can creat mock object for WebResource class, then mock accept method call.

In @BeforeClass/@Before JUnit test method write something like (Mockito example)

WebResource res = mock(WebResource.class);
when(res.accept(something)).thenReturn(thatWhatYouWant);

Then in your tests you can use res object as if it was real object and call mock method on it. Instead of returning value you can also throw exceptions. Mockito is pretty cool.

Piotr Kochański
  • 21,862
  • 7
  • 70
  • 77
  • 1
    This is the correct answer. Making a whole web service is spending effort on testing the Jersey code when I already know that works. I want to test my code. I ended up having to refactor the calls to the Jersey code to another class, adding a setter, and mocking out the new class. This worked perfectly and allowed me to test the different scenarios I actually wanted to test. My team is using JMock so I ended up using that, but any mocking framework will do. – BennyMcBenBen Jun 13 '12 at 17:27
  • 1
    I am a little confused since the Mockito website states "Don't mock type you don't own!" Where do we draw a line as to what can we mock versus not. Is there a better way? – Mr. Doomsbuster Jun 20 '17 at 14:23
14

Typically what you are really after is "does the way I use the Jersey Client DSL produce a request to the correct URL with the correct payload and URL parameters". Testing this with Mockito is really verbose and the setup code will usually end up looking something like this:

    when(authentication.queryParam(eq("sa"), anyBoolean())).thenReturn(testAuthentication);
    when(testAuthentication.resolveTemplate("channel", "smf")).thenReturn(testAuthentication);
    when(testAuthentication.request(
            MediaType.APPLICATION_JSON_TYPE)).thenReturn(mockRequestBuilder);
    when(mockRequestBuilder.post(any(Entity.class))).thenReturn(mockResponse);
    when(mockResponse.readEntity(ResponseWrapper.class)).thenReturn(successfulAuthResponse());

And this is basically just for a single REST request. It's overly verbose, and instead of testing the hoped outcome you are just replicating the steps you think are correct in using the Jersey Client DSL.

Instead of the above, I would aim for mocking a simple service. For this I've used WireMock which starts a Jetty server and where I can stub things like "expect a request to this URL, respond with this message and verify that the payload is this".

I know this is edging on an integration test and it is a bit slower than just using Mockito but I value testing the real outcome and I value the readability of the tests way more in this case.

Setup for a WireMock based Jersey Client test looks something like this:

@Test
public void exactUrlOnly() {
    stubFor(get(urlEqualTo("/some/thing"))
            .willReturn(aResponse()
                .withHeader("Content-Type", "text/plain")
                .withBody("Hello world!")));

   assertThat(testClient.get("/some/thing").statusCode(), is(200));
   assertThat(testClient.get("/some/thing/else").statusCode(), is(404));
}
vertti
  • 7,539
  • 4
  • 51
  • 81
  • +1 for WireMock - great tool. In my case, Mockito could not mock WebResource.Builder (final), solved with WireMock – denismo Apr 22 '16 at 04:13
0

Just implement a work-alike service and in your unit test setup start the service using HttpServerFactory.

Chase
  • 3,123
  • 1
  • 30
  • 35