Java RESTful API integration testing

This post will focus on basic principles and mechanics of writing Java integration tests for a RESTful API (with a JSON payload).

The goal is to provide an introduction to the technologies and to write some tests for basic correctness. The examples will consume the latest version of the GitHub REST API.

For an internal application, this kind of testing will usually run as a late step in a Continuous Integration process, consuming the REST API after it has already been deployed.

When testing a REST resource, there are usually a few orthogonal responsibilities the tests should focus on:

  • the HTTP response code
  • other HTTP headers in the response
  • the payload (JSON, XML)

Each test should only focus on a single responsibility and include a single assertion. Focusing on a clear separation always has benefits, but when doing this kind of black box testing it’s even more important, as the general tendency is to write complex test scenarios in the very beginning.

Another important aspect of the integration tests is adherence to the Single Level of Abstraction Principle – the logic within a test should be written at a high level. Details such as creating the request, sending the HTTP request to the server, dealing with IO, etc should not be done inline but via utility methods.

Testing the HTTP response code

@Test
public void givenUserDoesNotExists_whenUserInfoIsRetrieved_then404IsReceived()
      throws ClientProtocolException, IOException{
   // Given
   String name = randomAlphabetic( 8 );
   HttpUriRequest request = new HttpGet( "https://api.github.com/users/" + name );
   
   // When
   HttpResponse httpResponse = httpClient.execute( request );
   
   // Then
   RestAssert.assertResponseCodeIs( httpResponse, 404 );
}

This is a rather simple test, which verifies that a basic happy path is working, without adding to much complexity to the test suite. If, for whatever reason it fails, then there is no need to look at any other test for this URL until this is fixed. Because verifying the response code is one of the most common assertions of the integration testing suite, a custom assertion is used.

public static void assertResponseCodeIs
      ( final HttpResponse response, final int expectedCode ){
   final int statusCode = httpResponse.getStatusLine().getStatusCode();
   assertEquals( expectedCode, statusCode );
}

Testing other headers of the HTTP response

@Test
public void givenRequestWithNoAcceptHeader_whenRequestIsExecuted_thenDefaultResponseContentTypeIsJson()
      throws ClientProtocolException, IOException{
   // Given
   String jsonMimeType = "application/json";
   HttpUriRequest request = new HttpGet( "https://api.github.com/users/eugenp" );
   
   // When
   HttpResponse response = this.httpClient.execute( request );
   
   // Then
   String mimeType = EntityUtils.getContentMimeType( response.getEntity() );
   assertEquals( jsonMimeType, mimeType );
}

This ensures that the response when requesting the details of the user is actually JSON. There is a logical progression of the functionality under test – first the response code, to ensure that the request was OK, then the mime type of the request, and only then the verification that the actual JSON is correct.

Testing the JSON payload of the HTTP response

@Test
public void givenUserExists_whenUserInformationIsRetrieved_thenRetrievedResourceIsCorrect()
      throws ClientProtocolException, IOException{
   // Given
   HttpUriRequest request = new HttpGet( "https://api.github.com/users/eugenp" );
   
   // When
   HttpResponse response = new DefaultHttpClient().execute( request );
   
   // Then
   GitHubUser resource =
      RetrieveUtil.retrieveResourceFromResponse( response, GitHubUser.class );
   assertThat( "eugenp", Matchers.is( resource.getLogin() ) );
}

In this case, I know the default representation of GitHub resources is JSON, but usually the Content-Type header of the response should be tested alongside the Accept header of the request – the client asks for a particular type of representation via Accept, which the server should honor.

The Utilities for testing

Here are the utilities that enable the tests to remain at a higher level of abstraction:

- decorate the HTTP request with the JSON payload (or directly with the POJO):

public static < T >HttpEntityEnclosingRequest decorateRequestWithResource
      ( final HttpEntityEnclosingRequest request, final T resource )
      throws IOException{
   Preconditions.checkNotNull( request );
   Preconditions.checkNotNull( resource );
   
   final String resourceAsJson = JsonUtil.convertResourceToJson( resource );
   return JsonUtil.decorateRequestWithJson( request, resourceAsJson );
}

public static HttpEntityEnclosingRequest decorateRequestWithJson
      ( final HttpEntityEnclosingRequest request, final String json )
      throws UnsupportedEncodingException{
   Preconditions.checkNotNull( request );
   Preconditions.checkNotNull( json );
   
   request.setHeader( HttpConstants.CONTENT_TYPE_HEADER, "application/json" );
   request.setEntity( new StringEntity( json ) );
   
   return request;
}

- retrieve the JSON payload (or directly the POJO) from the HTTP response:

public static String retrieveJsonFromResponse( final HttpResponse response )
      throws IOException{
   Preconditions.checkNotNull( response );
   
   return IOUtils.toString( response.getEntity().getContent() );
}

public static < T >T retrieveResourceFromResponse
      ( final HttpResponse response, final Class< T > clazz ) throws IOException{
   Preconditions.checkNotNull( response );
   Preconditions.checkNotNull( clazz );
   
   final String jsonFromResponse = retrieveJsonFromResponse( response );
   return ConvertUtil.convertJsonToResource( jsonFromResponse, clazz );
}

- conversion utilities to and from java object (POJO) to JSON:

public static < T >String convertResourceToJson( final T resource )
      throws IOException{
   Preconditions.checkNotNull( resource );
  
  return new ObjectMapper().writeValueAsString( resource );
}

public static < T >T convertJsonToResource
      ( final String json, final Class< T > clazzOfResource ) throws IOException{
  Preconditions.checkNotNull( json );
  Preconditions.checkNotNull( clazzOfResource );
  
  return new ObjectMapper().readValue( json, clazzOfResource );
}

Dependencies

The utilities and tests make use of of the following libraries, all available in Maven central:

Conclusion

This is only one part of what the complete integration testing suite should be. The tests focus on ensuring basic correctness for the REST API, without going into more complex scenarios, discoverability of the API, consumption of different representations for the same resource or other more advanced areas. I will address these in a further post, in the meantime checkout the full project on github.

Reference: Introduction to Java integration testing for a RESTful API from our JCG partner Eugen Paraschiv at the baeldung blog.

Related Articles :
Related Whitepaper:

Functional Programming in Java: Harnessing the Power of Java 8 Lambda Expressions

Get ready to program in a whole new way!

Functional Programming in Java will help you quickly get on top of the new, essential Java 8 language features and the functional style that will change and improve your code. This short, targeted book will help you make the paradigm shift from the old imperative way to a less error-prone, more elegant, and concise coding style that’s also a breeze to parallelize. You’ll explore the syntax and semantics of lambda expressions, method and constructor references, and functional interfaces. You’ll design and write applications better using the new standards in Java 8 and the JDK.

Get it Now!  

One Response to "Java RESTful API integration testing"

  1. Chanoch says:

    Generally useless article now – code is missing from github and the new version of httpclient seems to be missing the HttpConstants class now – probably deprecated and removed?

Leave a Reply


3 × = six



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.

Sign up for our Newsletter

20,709 insiders are already enjoying weekly updates and complimentary whitepapers! Join them now to gain exclusive access to the latest news in the Java world, as well as insights about Android, Scala, Groovy and other related technologies.

As an extra bonus, by joining you will get our brand new e-books, published by Java Code Geeks and their JCG partners for your reading pleasure! Enter your info and stay on top of things,

  • Fresh trends
  • Cases and examples
  • Research and insights
  • Two complimentary e-books