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.