1. Обзор
В этом руководстве рассматриваются основные принципы и механизмы тестирования REST API с помощью живых интеграционных тестов (с полезной нагрузкой JSON).
Основная цель — дать введение в тестирование базовой корректности API — и мы собираемся использовать последнюю версию GitHub REST API для примеров.
Для внутреннего приложения этот тип тестирования обычно выполняется на позднем этапе процесса непрерывной интеграции, используя REST API после того, как он уже был развернут.
При тестировании ресурса REST обычно есть несколько ортогональных обязанностей, на которых должны сосредоточиться тесты:
- код ответа HTTP ****
- другие заголовки HTTP в ответе
- полезная нагрузка ( JSON, XML)
Каждый тест должен фокусироваться только на одной ответственности и включать одно утверждение. Сосредоточение внимания на четком разделении всегда имеет свои преимущества, но при выполнении такого рода тестирования черного ящика это еще более важно, поскольку общая тенденция заключается в написании сложных тестовых сценариев в самом начале.
Еще одним важным аспектом интеграционных тестов является соблюдение принципа единого уровня абстракции
— логика внутри теста должна быть написана на высоком уровне. Такие детали, как создание запроса, отправка HTTP-запроса на сервер, работа с вводом-выводом и т. д., должны выполняться не в процессе, а с помощью служебных методов.
2. Тестирование кода состояния
@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));
}
Это довольно простой тест — он проверяет, работает ли базовый счастливый путь , не слишком усложняя набор тестов.
Если по какой-либо причине это не удается, то нет необходимости смотреть какие-либо другие тесты для этого URL-адреса, пока это не будет исправлено.
3. Тестирование типа носителя
@Test
public void
givenRequestWithNoAcceptHeader_whenRequestIsExecuted_thenDefaultResponseContentTypeIsJson()
throws ClientProtocolException, IOException {
// Given
String jsonMimeType = "application/json";
HttpUriRequest request = new HttpGet( "https://api.github.com/users/foreach" );
// When
HttpResponse response = HttpClientBuilder.create().build().execute( request );
// Then
String mimeType = ContentType.getOrDefault(response.getEntity()).getMimeType();
assertEquals( jsonMimeType, mimeType );
}
Это гарантирует, что ответ действительно содержит данные JSON.
Как вы могли заметить, мы следуем логической последовательности тестов — сначала код состояния ответа (чтобы убедиться, что запрос был в порядке), затем тип носителя ответа, и только в следующем тесте мы рассмотрим фактическая полезная нагрузка JSON.
4. Тестирование полезной нагрузки JSON
@Test
public void
givenUserExists_whenUserInformationIsRetrieved_thenRetrievedResourceIsCorrect()
throws ClientProtocolException, IOException {
// Given
HttpUriRequest request = new HttpGet( "https://api.github.com/users/foreach" );
// When
HttpResponse response = HttpClientBuilder.create().build().execute( request );
// Then
GitHubUser resource = RetrieveUtil.retrieveResourceFromResponse(
response, GitHubUser.class);
assertThat( "foreach", Matchers.is( resource.getLogin() ) );
}
В этом случае я знаю, что представлением ресурсов GitHub по умолчанию является JSON, но обычно заголовок ответа
<code>
Content-Type
следует тестировать вместе с заголовком Accept
запроса — клиент запрашивает определенный тип представления через Accept
, который сервер должен уважать.
5. Утилиты для тестирования
Мы собираемся использовать Jackson 2, чтобы преобразовать необработанную строку JSON в безопасную для типов сущность Java:
public class GitHubUser {
private String login;
// standard getters and setters
}
Мы используем только простую утилиту, чтобы тесты были чистыми, читабельными и на высоком уровне абстракции:
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);
}
Обратите внимание, что Джексон игнорирует неизвестные свойства , которые API GitHub отправляет нам — это просто потому, что представление пользовательского ресурса на GitHub становится довольно сложным — и нам не нужна эта информация здесь.
6. Зависимости
Утилиты и тесты используют следующие библиотеки, доступные в Maven Central:
- HTTP-клиент
- Джексон 2
- Хэмкрест (по желанию)
7. Заключение
Это только одна часть того, каким должен быть полный комплект интеграционного тестирования. Тесты сосредоточены на обеспечении базовой корректности REST API , не вдаваясь в более сложные сценарии .
Например, не рассматриваются следующие аспекты: возможность обнаружения API, использование различных представлений для одного и того же ресурса и т. д.
Реализацию всех этих примеров и фрагментов кода можно найти на Github — это проект на основе Maven, поэтому его должно быть легко импортировать и запускать как есть.