2023. 2. 10. 17:14ㆍjava
1. Overview
In this tutorial, we'll focus on the basic principles and mechanics of testing a REST API with live Integration Tests (with a JSON payload).
Our main goal is to provide an introduction to testing the basic correctness of the API, and we'll use 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's 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 because the general tendency is to write complex test scenarios in the very beginning.
Another important aspect of integration tests is adherence to the Single Level of Abstraction Principle; we should write the logic within a test at a high level. Details such as creating the request, sending the HTTP request to the server, dealing with IO, etc., shouldn't 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));
}Copy
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.
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 );
}Copy
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.
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() ) );
}Copy
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.
5. Utilities for Testing
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
}Copy
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);
}Copy
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.
6. Dependencies
The utilities and tests make use of the following libraries, all of which are available in Maven central:
- HttpClient
- Jackson 2
- Hamcrest (optional)
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, we didn't cover the following: discoverability of the API, consumption of different representations for the same Resource, etc.
The implementation of all of 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.
'java' 카테고리의 다른 글
쿠버네티스, 왜 필요할까 (0) | 2023.02.21 |
---|---|
CI/CD 개념 , Gitops introduction (0) | 2023.02.21 |
전자정부 클라우드 플랫폼 - MSA 템플릿 실행환경 (0) | 2023.02.09 |
[Java] 중복 키 허용 MultiValueMap 와 HashMap 차이 (0) | 2023.01.18 |
(고민중) 한글문서 썸네일파일 만들기 (0) | 2023.01.13 |