
Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:
Once the early-adopter seats are all used, the price will go up and stay at $33/year.
Last updated: January 26, 2025
In this tutorial, we’ll focus on the principles and mechanics of testing a REST API through integration tests. Specifically, we’ll be testing the API with live requests and JSON payloads to ensure its correctness and behavior.
API integration testing involves testing the interactions between our application and its external dependencies (such as databases, third-party services, or other APIs). The goal is to ensure that different components of the system work together as expected. Unlike unit tests focusing on individual components, integration tests simulate real-world usage by sending actual HTTP requests to our API and checking the responses, ensuring that everything works together as expected.
While unit tests ensure individual pieces of code work as intended, they can’t catch issues that arise from the integration of these components. Integration tests address these concerns by checking how different parts of the system communicate, ensuring that miscommunications or errors between components don’t disrupt the overall functionality.
Additionally, they give teams confidence that the system will work as expected in production, reducing the risk of failures after deployment.
Typically, API integration tests are conducted after unit tests but before major releases. After unit testing confirms that individual components are functioning properly, integration tests ensure the components work together as expected.
Integration testing should also be performed before major releases to catch any potential regressions or integration issues that could arise with new features or updates.
To ensure the API behaves as expected when a user does not exist, we can test the response 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 we don’t need to look at any other test for this URL until we fix it.
We can also test on the media type to ensure that the default Content-Type of the response is JSON:
@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 we can see, we’re following a logical progression of tests. First is the Response Status Code (to ensure the request was OK), and then the Media Type of the Response. Only in the next test will we look at the actual JSON payload.
To verify that the API returns the correct data, we can test the contents of the JSON payload. Here, we check that the information for an existing user matches the expected values:
@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, the default representation of the 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.
We’ll 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. This is simply because the Representation of a User Resource on GitHub gets pretty complex, and we don’t need any of that information here.
The utilities and tests make use of the following libraries, all of which are available in Maven Central:
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, we didn’t cover the following: discoverability of the API, consumption of different representations for the same Resource, etc.