Copy of Test a REST API with Java

1. Overview

This tutorial focuses 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 is 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.

Further reading:

Integration Testing in Spring

A quick guide to writing integration tests for a Spring Web application.

Read more

Testing in Spring Boot

Learn about how the Spring Boot supports testing, to write unit tests
efficiently.

Read more

A Guide to REST-assured

Explore the basics of REST-assured – a library that simplifies the
testing and validation of REST APIs.

Read more

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 too 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 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
over on
Github
– this is a Maven-based project, so it should be easy to import
and run as it is.

Leave a Reply

Your email address will not be published.