null

Симуляция долгих ответов в приложении на Spring Boot

Оригинал этой статьи - труд автора Bubu Tripathy под названием Simulate Slow Responses in a Spring Boot Application

Настройка окружения

Код, который вы увидите в статье, написан с применением следующих версий инструментов: Spring Boot 2.5.4, Java 11, Maven 3.8.3, JUnit 4.13.2 и WireMock 2.27.2.

И так, настройка рабочего окружения начинается с добавления Maven-зависимости в файл pom.xml нашего проекта:

<dependency>
    <groupId>com.github.tomakehurst</groupId>
    <artifactId>wiremock</artifactId>
    <version>2.27.2</version>
</dependency>

Следующим шагом является добавление в проект конфигурационного класса, позволяющего настроить сервер WireMock:

import com.github.tomakehurst.wiremock.WireMockServer;
import com.github.tomakehurst.wiremock.client.WireMock;

import static com.github.tomakehurst.wiremock.client.WireMock.*;

public class WireMockConfig {
    
    private WireMockServer wireMockServer;

    public void start() {
        wireMockServer = new WireMockServer(options().port(8080));
        wireMockServer.start();
        configureFor("localhost", 8080);
    }

    public void stop() {
        wireMockServer.stop();
    }

    public void reset() {
        WireMock.reset();
    }

    public void stubSlowResponse() {
        stubFor(get(urlEqualTo("/slow-response"))
                .willReturn(aResponse()
                        .withFixedDelay(5000)
                        .withStatus(200)));
    }

}

В этом классе, мы создаем экземпляр сервера WireMock и настраиваем его на прослушку порта 8080. Также в нём мы определяем метод stubSlowResponse(), который является WireMock-заглушкой для конечной точки (endpoint). Точка будет доступна по GET-запросу на адрес /slow-response. Ответ от неё будет возвращаться с фиксированной задержкой в 5 секунд и кодом состояния - 200 (OK). Таким образом будет достигаться имитация долгого ответа от удаленного сервера.

Тестирование приложения

Теперь, когда мы настроили сервер WireMock, мы можем использовать его для тестирования нашего Spring Boot приложения.

В следующем примере мы создаём простой REST-контроллер, который выполняет HTTP-запрос к конечной точке /slow-response и возвращает тело ответа:

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
public class SlowResponseController {

    @GetMapping("/slow-response")
    public String getSlowResponse() {
        RestTemplate restTemplate = new RestTemplate();
        String response = restTemplate.getForObject("http://localhost:8080/slow-response", String.class);
        return response;
    }
}

Для тестирования контроллера воспользуемся фреймворком JUnit и создадим соответствующий тестовый класс:

import com.github.tomakehurst.wiremock.junit.WireMockRule;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;

import static com.github.tomakehurst.wiremock.client.WireMock.*;

@RunWith(SpringRunner.class)
@WebMvcTest(SlowResponseController.class)
public class SlowResponseControllerTest {
  @Autowired
  private MockMvc mockMvc;
  
  @MockBean
  private RestTemplate restTemplate;
  
  @Rule
  public WireMockRule wireMockRule = new WireMockRule(8080);
  
  @Autowired
  private WireMockConfig wireMockConfig;
  
  @Before
  public void setup() {
      wireMockConfig.start();
  }
  
  @Test
  public void should_return_slow_response() throws Exception {
      wireMockConfig.stubSlowResponse();
  
      mockMvc.perform(MockMvcRequestBuilders.get("/slow-response"))
              .andExpect(MockMvcResultMatchers.status().isOk());
  
      verify(getRequestedFor(urlEqualTo("/slow-response")));
  }
}

 

В этом тестовом классе мы используем правило WireMockRule для запуска нашего сервера WireMock на порту 8080. Мы также используем класс WireMockConfig для инициализации заглушки долгого ответа для конечной точки /slow-response. Наконец, с помощью класса MockMvc мы выполняем HTTP GET-запрос к нашему контроллеру и проверяем, что код состояния ответа равен 200.

Имитация долгого ответа с динамической задержкой

В предыдущем примере мы использовали метод withFixedDelay() для моделирования фиксированной задержки в 5 секунд. Однако в реальных сценариях время отклика внешнего сервиса может меняться в зависимости от различных факторов, таких как: состояние сети, загрузка сервера и т.д. В подобных случаях более реалистичным может оказаться имитация динамической задержки.

Для симуляции динамической задержки, вместо метода withFixedDelay() можно использовать метод withUniformRandomDelay(). Этот метод принимает два аргумента - минимальное и максимальное время задержки в миллисекундах - и генерирует случайную задержку между этими двумя значениями для каждого нового ответа.

Приведем пример его использования:

public void stubDynamicDelayResponse() {
    stubFor(get(urlEqualTo("/dynamic-delay-response"))
            .willReturn(aResponse()
                    .withUniformRandomDelay(1000, 5000)
                    .withStatus(200)));
}

В данном примере мы моделируем случайную задержку в диапазоне между 1 и 5 секундами для каждого нового ответа.

Имитация медленного отклика с распределением задержки

В некоторых случаях время отклика внешнего сервиса может подчиняться определенному распределению, например, нормальному (Распределение Гаусса) или пуассоновскому (Распределение Пуассона). Для моделирования таких сценариев вместо метода withFixedDelay() можно соответственно использовать метод withLogNormalDistribution() или withPoissonDistribution().

Приведем пример использования метода withLogNormalDistribution() для моделирования времени отклика с логнормальным распределением:

public void stubLogNormalDelayResponse() {
    stubFor(get(urlEqualTo("/log-normal-delay-response"))
            .willReturn(aResponse()
                    .withLogNormalDistribution(5000, 1.0)
                    .withStatus(200)));
}

В данном примере мы имитируем время отклика с логнормальным распределением, где средняя задержка составляет 5 секунд, а стандартное отклонение равно 1,0. Используя различные методы распределения, мы можем смоделировать широкий спектр сценариев времени отклика и проверить, как наше приложение их отрабатывает.

Имитация медленного отклика с помощью последовательности задержек

В некоторых случаях нам может быть необходимо смоделировать последовательность ответов с увеличивающимся временем задержки, например, когда мы хотим увидить как наше приложение отрабатывает таймауты. Для имитации подобных сценариев вместо метода withFixedDelay() мы можем использовать метод withDelaySequence().

Приведем пример использования метода withDelaySequence():

public void stubDelaySequenceResponse() {
    stubFor(get(urlEqualTo("/delay-sequence-response"))
            .willReturn(aResponse()
                    .withDelaySequence(1000, 2000, 3000, 4000, 5000)
                    .withStatus(200)));
}

В данном примере мы моделируем последовательность ответов с задержками в 1, 2, 3, 4 и 5 секунд. Используя последовательность задержек, мы можем протестировать, как наше приложение обрабатывает различные сценарии таймаутов, и убедиться, что оно адекватно справляется с такими ситуациями.

 

И так, в этой статье мы рассмотрели, как использовать инструмент WireMock для имитации медленных ответов в приложении на Spring Boot. Используя различные методы генерации задержки, мы можем имитировать различные сценарии времени отклика и проверить, как наше приложение их отрабатывает.