Начало работы с MockWebServer и JUnit

MockWebServer — полезная библиотека для имитации зависимых API, от которых зависит текущий компонент(тестируемый). Такие имитационные API чрезвычайно полезны в архитектуре микросервисов, где мы разрабатываем несколько зависимых сервисов одновременно.

В этом уроке мы научимся настраивать MockWebServer в тестах JUnit 5. Мы будем использовать Spring WebClient в качестве HTTP-клиента для вызова фиктивных API.

1. Зависимости Maven

MockWebServer является частью библиотеки okhttp3, поэтому нам необходимо импортировать следующие две зависимости.

<dependency><groupId>com.squareup.okhttp3</groupId><artifactId>okhttp</artifactId><version>4.10.0</version><scope>test</scope></dependency><dependency><groupId>com.squareup.okhttp3</groupId><artifactId>mockwebserver</artifactId><version>4.10.0</version><scope>test</scope></dependency>

2. Запуск и остановка MockWebServer

Мы можем использовать MockWebServer, аналогично другим подобным библиотекам, таким как WireMock. Обычно мы

  • настроить макеты
  • запустить сервер перед началом тестов
  • запустите тесты и проверьте ответы
  • остановить сервер после окончания тестов

В следующем примере используются хуки @BeforeAll и @AfterAll для запуска и остановки сервера. По умолчанию сервер запускается на порту 8080. Мы можем использовать другой порт, указав его в методе start().

public class MockWebServerTests {public static MockWebServer server;@BeforeAllstatic void setUp() throws IOException {server = new MockWebServer();server.start(8080);}@AfterAllstatic void tearDown() throws IOException {server.shutdown();}}

При желании мы можем создать новый экземпляр сервера для каждого модульного теста.

public void test() throws Exception {MockWebServer server = new MockWebServer();server.start(8080);//---test---server.shutdown();}

3. Настройка макетов

3.1 Использование Server.enqueue()

Для настройки фиктивных ответов мы можем использовать метод Server.enqueue(). Мы можем поставить в очередь столько ответов, сколько захотим. Затем мы можем нажать на URL-адрес фиктивного API, чтобы последовательно извлечь фиктивные ответы.

public void test() throws Exception {MockWebServer server = new MockWebServer();server.enqueue(new MockResponse().setBody("message 1"));server.enqueue(new MockResponse().setBody("message 2"));server.enqueue(new MockResponse().setBody("message 3"));server.start();//more code}

3.2 Использование диспетчера

Возврат поставленных в очередь ответов не подходит для всех условий. Мы можем использовать класс Dispatcher для разработки собственной логики возврата ответов API.

В следующем примере мы используем диспетчер для сопоставления API URI входящего запроса, а затем возвращаем ответ для соответствующего URI.

public class MockWebServerTests {public static MockWebServer server;final static Dispatcher dispatcher = new Dispatcher() {@Overridepublic MockResponse dispatch(RecordedRequest request) throws InterruptedException {switch(request.getPath()) {case "/api-url-one":return new MockResponse().setResponseCode(201);case "/api-url-two":return new MockResponse().setHeader("x-header-name", "header-value").setResponseCode(200).setBody("<response />");case "/api-url-three":return new MockResponse().setResponseCode(500).setBodyDelay(5000, TimeUnit.SECONDS).setChunkedBody("<error-response />", 5);case "/api-url-four":return new MockResponse().setResponseCode(200).setBody("{\"data\":\"\"}").throttleBody(1024, 5, TimeUnit.SECONDS);}return new MockResponse().setResponseCode(404);}};@BeforeAllstatic void setUp() throws IOException {server = new MockWebServer();server.setDispatcher(dispatcher);server.start(8080);}//more test code}

4. Написание тестов

4.1. Обычные тесты JUnit

После настройки моков мы можем обратиться к API моков с помощью Spring WebClient. Чтобы получить URL хоста API, используйте метод server.getHostName().

WebClient webClient = WebClient.create(String.format("http://%s:8080", server.getHostName()));

Наконец, обратитесь к фиктивному API и передайте параметры и тело запроса по мере необходимости.

Mono<String> apiResponse = webClient.post().uri("/api-url-two").body(Mono.just("<data />"), String.class).header("Authorization", "Basic " +Base64Utils.encodeToString(("username:password").getBytes(UTF_8))).retrieve().bodyToMono(String.class);

Как только ответ API станет доступен, мы сможем спроецировать StepVerifier Reactor для тестирования этих асинхронных ответов.

StepVerifier.create(apiResponse).expectNext("<response />").verifyComplete();

4.2 Ошибочные состояния

Ответы API не всегда будут успешными. Мы можем получать разные коды ошибок и другие сбои, такие как проблемы с сетью и задержки. MockWebServer поддерживает такие виды ошибочных фиктивных ответов.

Например, мы можем протестировать логику тайм-аута и отложенные ответы с помощью метода setBodyDelay().

new MockResponse().setResponseCode(200).setBodyDelay(5000, TimeUnit.MILLISECONDS).setBody("<data-response />");

Для тестирования медленной сети мы можем использовать метод setChunkedBody() для отправки ответа порциями. Данный мок отправит ответ 5 порциями.

new MockResponse().setResponseCode(200).setChunkedBody("<data-response />", 5);

5. Проверка статистики сервера

Иногда важно проверить, сколько раз запрос был выполнен на фиктивном сервере. Это особенно полезно, когда мы реализуем и тестируем логику повтора. Мы можем использовать экземпляр RecordedRequest для получения сведений о HTTP-запросах MockWebServer, чтобы убедиться, что наш WebClient отправил их правильно.

RecordedRequest request = server.takeRequest();assertEquals("/api-url-two", request.getPath());assertEquals("POST", request.getMethod());assertNotNull(request.getHeader("Authorization"));assertEquals("<data />", request.getBody().readUtf8());

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

В этом уроке мы научились использовать MockWebServer для имитации API и ответов, а затем использовать эти API с помощью WebClient.

Мы научились запускать и останавливать сервер, настраивать фиктивные объекты, писать тесты на успешность и ошибки, проверять сведения об отправленных запросах и т. д. Есть и другие популярные альтернативы, которые вы можете рассмотреть, например, WireMock.

Исходный код на Github

Прокрутить вверх