«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/eugenp" );

   // 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/eugenp" );

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

    // Then
    GitHubUser resource = RetrieveUtil.retrieveResourceFromResponse(
      response, GitHubUser.class);
    assertThat( "eugenp", Matchers.is( resource.getLogin() ) );
}

В этом случае я знаю, что представлением ресурсов GitHub по умолчанию является JSON, но обычно заголовок ответа \u003ccode\u003eContent-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:

    HttpClient Jackson 2 Hamcrest (необязательно)

7. Заключение

Это только одна часть того, каким должен быть полный комплект интеграционного тестирования. Тесты сосредоточены на обеспечении базовой корректности REST API, не вдаваясь в более сложные сценарии.

Например, не рассматриваются следующие аспекты: возможность обнаружения API, использование различных представлений для одного и того же ресурса и т. д.

Реализацию всех этих примеров и фрагментов кода можно найти на Github — это проект на основе Maven, поэтому его легко импортировать и запускать как есть.