Test a REST API with Java

Table of Contents

1. Overview

This tutorial will focus on the basic principles and mechanics of testing a REST API with live Integration Tests (with a JSON payload).

The main goal is to provide an introduction to testing the basic correctness of  the API – and we’re going to be using the latest version of the GitHub REST API for the examples.

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.

2. Testing the Status Code

@Test
public void givenUserDoesNotExists_whenUserInfoIsRetrieved_then404IsReceived()
      throws ClientProtocolException, IOException{
   // Given
   String name = RandomStringUtils.randomAlphabetic( 8 );
   HttpUriRequest request = new HttpGet( "https://api.github.com/users/" + name );

   // When
   HttpResponse httpResponse = HttpClientBuilder.create().build().execute( request );

   // Then
   assertThat(httpResponse.getStatusLine().getStatusCode(), equalTo(HttpStatus.SC_NOT_FOUND));
}

This is a rather simple test – it 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.

3. Testing the Media Type

@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 = HttpClientBuilder.create().build().execute( request );

   // Then
   String mimeType = ContentType.getOrDefault(response.getEntity()).getMimeType();
   assertEquals( jsonMimeType, mimeType );
}

This ensures that the Response actually contains JSON data.

As you might have noticed, we’re following a logical progression of tests – first the Response Status Code (to ensure that the request was OK), then the Media Type of the Response, and only in the next test will we look at the actual JSON payload.

4. Testing the JSON Payload

@Test
public void 
  givenUserExists_whenUserInformationIsRetrieved_thenRetrievedResourceIsCorrect()
  throws ClientProtocolException, IOException{
    // Given
    HttpUriRequest request = new HttpGet( "https://api.github.com/users/eugenp" );

    // When
    HttpResponse response = HttpClientBuilder.create().build().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.

5. Utilities for Testing

We’re going to use Jackson 2 to unmarshall the raw JSON String into a type-safe Java Entity:

public class GitHubUser {

    private String login;

    // standard getters and setters
}

We’re only using a simple utility to keep the tests clean, readable and at a high level of abstraction:

public static <T> T retrieveResourceFromResponse(HttpResponse response, Class<T> clazz) 
  throws IOException {
    String jsonFromResponse = EntityUtils.toString(response.getEntity());
    ObjectMapper mapper = new ObjectMapper().
      configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    return mapper.readValue(jsonFromResponse, clazz);
}

Notice that Jackson is ignoring unknown properties that the GitHub API is sending our way – that’s simply because the Representation of a User Resource on GitHub gets pretty complex – and we don’t need any of that information here.

6. Dependencies

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

7. 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,

For example, the following are not covered: Discoverability of the API, consumption of different representations for the same Resource, etc.

The implementation of all these examples and code snippets can be found in my github project – this is an Eclipse based project, so it should be easy to import and run as it is.

I usually post about REST APIs and HTTP on Google+ - you can follow me there:

>> GET THE EBOOKS <<
Get the eBooks and Learn to Build a Simple App
×
Build a Simple but Working App with Spring

, ,

  • http://twitter.com/fuchshuber Josef Fuchshuber

    We are using REST-assured to test and validate our REST-API. In combination with the maven-failsafe-plugin, maven-jetty-plugin and Jenkins everything works fine! Link: http://code.google.com/p/rest-assured/

    • vivek pradhan

      can u post your REST-assured code / send it to me. I am also looking at using REST assured.

      • baeldung

        Sure, I’m planning to write a folloup article using only rest-assured; for the time being however, you can always check out the github project (link at the end of the post) for about 300 tests done with rest-assured.
        Eugen.

  • http://gapkandroid.blogspot.com/ Android Apps

    I like your article about Integration testing of a REST API | baeldung Perfect just what I was looking for! .

  • Anonymous

    This is very helpful series of articles. Very nice job for a newbie who want to start with Spring3.1 restful. I am wandering how can I test the post/put/delete methods from command line, for example using curl. I am able to do the get. But for the post, it always complains ” The server refused this request because the request etity is in a format not supported by the requested resource for the requested m
    thod ().”. My understanding is that the api is expecting foo entity. But how could this be sent from command line (or javascript eventually). My command line is something like:

    curl -u eparaschiv:eparaschiv -v -H “Accept: application/json” -H “Content-type: application/*” -X POST -d ‘{“Foo”:{“name”:”foo4″}}’ http://localhost:8080/rest/api/admin/foo/

    I am wondering if anyone here knows how to do this.

    Thanks a lot,
    Simon

    • Eugen

      Hi,
      Just a quick note about your curl command – the Accept header is application/json, so your Content-type should be the same. Other than that, you should be able to consume the API via curl commands without any problems. Also, you are asking how to send the Foo resource in the POST, but it looks like you’re already doing that, so you should be fine.
      Thanks for the interesting feedback.
      Eugen.

      • Anonymous

        Thanks for the replay. I tried Context-type with application/json, it’s giving me same error message.I tried different way on the data part and with no luck either.

    • Abdullah

      Please try this,
      curl -k -X POST -H ‘Content-Type: application/json’ –data-binary ‘{“Foo”:{“name”:”foo4″}}’ http://localhost:8080/rest/api/admin/foo -u admin:admin

  • braghome

    your github link for the testing code is returning a 404