Подтвердить что ты не робот

Как написать интеграционные тесты с помощью spring -cloud-netflix и feign

Я использую Spring -Cloud-Netflix для связи между микросервисами. Скажем, у меня есть две службы: Foo и Bar, а Foo потребляет одну из конечных точек Bar REST. Я использую интерфейс, аннотированный с помощью @FeignClient:

@FeignClient
public interface BarClient {
  @RequestMapping(value = "/some/url", method = "POST")
  void bazzle(@RequestBody BazzleRequest);
}

Тогда у меня есть класс обслуживания SomeService в Foo, который вызывает BarClient.

@Component
public class SomeService {
    @Autowired
    BarClient barClient;

    public String doSomething() {
      try {
        barClient.bazzle(new BazzleRequest(...));
        return "so bazzle my eyes dazzle";
      } catch(FeignException e) {
        return "Not bazzle today!";
      }

    }
}

Теперь, чтобы убедиться, что связь между службами работает, я хочу построить тест, который запускает настоящий HTTP-запрос на поддельный Bar-сервер, используя что-то вроде WireMock. Тест должен убедиться, что feign правильно декодирует ответ службы и сообщает об этом SomeService.

public class SomeServiceIntegrationTest {

    @Autowired SomeService someService;

    @Test
    public void shouldSucceed() {
      stubFor(get(urlEqualTo("/some/url"))
        .willReturn(aResponse()
            .withStatus(204);

      String result = someService.doSomething();

      assertThat(result, is("so bazzle my eyes dazzle"));
    }

    @Test
    public void shouldFail() {
      stubFor(get(urlEqualTo("/some/url"))
        .willReturn(aResponse()
            .withStatus(404);

      String result = someService.doSomething();

      assertThat(result, is("Not bazzle today!"));
    }
}

Как я могу вставить такой сервер WireMock в eureka, чтобы feign смог найти его и связаться с ним? Какая магия аннотаций мне нужна?

4b9b3361

Ответ 1

Используйте Spring RestTemplate вместо симулирования. RestTemplate также может разрешать имена служб через eureka, поэтому вы можете сделать что-то вроде этого:

@Component
public class SomeService {
   @Autowired
   RestTemplate restTemplate;

   public String doSomething() {
     try {
       restTemplate.postForEntity("http://my-service/some/url", 
                                  new BazzleRequest(...), 
                                  Void.class);
       return "so bazzle my eyes dazzle";
     } catch(HttpStatusCodeException e) {
       return "Not bazzle today!";
     }
   }
}

Это проще проверить с помощью Wiremock, чем притворяться.

Ответ 2

Вот пример использования WireMock для тестирования конфигурации SpringBoot с помощью клиента Fign и резервного копирования Hystrix.

Если вы используете Eureka в качестве открытия сервера, вам необходимо отключить его, установив свойство "eureka.client.enabled=false".

Во-первых, нам нужно включить конфигурацию Feign/Hystrix для нашего приложения:

@SpringBootApplication
@EnableFeignClients
@EnableCircuitBreaker
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

@FeignClient(
        name = "bookstore-server",
        fallback = BookClientFallback.class,
        qualifier = "bookClient"
)
public interface BookClient {

    @RequestMapping(method = RequestMethod.GET, path = "/book/{id}")
    Book findById(@PathVariable("id") String id);
}

@Component
public class BookClientFallback implements BookClient {

    @Override
    public Book findById(String id) {
        return Book.builder().id("fallback-id").title("default").isbn("default").build();
    }
}

Обратите внимание, что мы указываем резервный класс для клиента Feign. Класс Fallback будет вызываться каждый раз, когда происходит сбой вызова клиента (например, время ожидания соединения).

Для того, чтобы тесты работали, нам нужно настроить балансировщик ленты (будет использоваться внутренне клиентом Feign при отправке HTTP-запроса):

@RunWith(SpringRunner.class)
@SpringBootTest(properties = {
        "feign.hystrix.enabled=true"
})
@ContextConfiguration(classes = {BookClientTest.LocalRibbonClientConfiguration.class})
public class BookClientTest {

    @Autowired
    public BookClient bookClient;

    @ClassRule
    public static WireMockClassRule wiremock = new WireMockClassRule(
            wireMockConfig().dynamicPort()));

    @Before
    public void setup() throws IOException {
        stubFor(get(urlEqualTo("/book/12345"))
                .willReturn(aResponse()
                        .withStatus(HttpStatus.OK.value())
                        .withHeader("Content-Type", MediaType.APPLICATION_JSON)
                        .withBody(StreamUtils.copyToString(getClass().getClassLoader().getResourceAsStream("fixtures/book.json"), Charset.defaultCharset()))));
    }

    @Test
    public void testFindById() {
        Book result = bookClient.findById("12345");

        assertNotNull("should not be null", result);
        assertThat(result.getId(), is("12345"));
    }

    @Test
    public void testFindByIdFallback() {
        stubFor(get(urlEqualTo("/book/12345"))
                .willReturn(aResponse().withFixedDelay(60000)));

        Book result = bookClient.findById("12345");

        assertNotNull("should not be null", result);
        assertThat(result.getId(), is("fallback-id"));
    }

    @TestConfiguration
    public static class LocalRibbonClientConfiguration {
        @Bean
        public ServerList<Server> ribbonServerList() {
            return new StaticServerList<>(new Server("localhost", wiremock.port()));
        }
    }
}

Список ленточных серверов должен соответствовать URL-адресу (хосту и порту) нашей конфигурации WireMock.

Ответ 3

Вот пример, как выполнить проводку Feign и WireMock со случайным портом (на основе Spring -Boot github),

@RunWith(SpringRunner.class)
@SpringBootTest(properties = "google.url=http://google.com") // emulate application.properties
@ContextConfiguration(initializers = PortTest.RandomPortInitializer.class)
@EnableFeignClients(clients = PortTest.Google.class)
public class PortTest {

    @ClassRule
    public static WireMockClassRule wireMockRule = new WireMockClassRule(
        wireMockConfig().dynamicPort()
    );

    @FeignClient(name = "google", url = "${google.url}")
    public interface Google {    
        @RequestMapping(method = RequestMethod.GET, value = "/")
        String request();
    }

    @Autowired
    public Google google;

    @Test
    public void testName() throws Exception {
        stubFor(get(urlEqualTo("/"))
                .willReturn(aResponse()
                        .withStatus(HttpStatus.OK.value())
                        .withBody("Hello")));

        assertEquals("Hello", google.request());
    }


    public static class RandomPortInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
        @Override
        public void initialize(ConfigurableApplicationContext applicationContext) {

            // If the next statement is commented out, 
            // Feign will go to google.com instead of localhost
            TestPropertySourceUtils
                .addInlinedPropertiesToEnvironment(applicationContext,
                    "google.url=" + "http://localhost:" + wireMockRule.port()
            );
        }
    }
}

Альтернативно вы можете попробовать сыграть с System.setProperty() в @BeforeClass методе вашего теста.

Ответ 4

Вероятно, нет возможности связать WireMock напрямую с Eureka Server, но вы можете использовать другие варианты для настройки тестовой среды, в которой вы нуждаетесь.

  • В тестовой среде вы можете развернуть Eureka Service Registry в автономном контейнере сервлетов Jetty, и все аннотации будут работать так же, как в реальной производственной среде.
  • Если вы не хотите использовать реальную логику конечной точки BarClient, а интеграционный тест - это только реальный транспортный уровень http, то вы можете использовать Mockito для BarClient конечной точки.

Я полагаю, что для реализации 1 и 2 с помощью Spring -Boot вам нужно создать два отдельных приложения для тестовой среды. Один для Eureka Service Registry под Jetty, а другой для конечной точки BarClient под Jetty тоже.

Другое решение - вручную настроить Jetty и Eureka в контексте тестового приложения. Я думаю, что это лучший способ, но в таком случае вы должны понимать, что аннотации @EnableEurekaServer и @EnableDiscoveryClient выполняются с контекстом приложения Spring.

Ответ 5

В основном были два варианта проведения интеграционных тестов для приложений микросервисов:

  • Развертывание служб в тестовой среде и создание сквозных тесты
  • Издевательствование других микросервисов

Первый вариант имеет очевидный недостаток, связанный с проблемой развертывания всех зависимостей (другие службы, базы данных и т.д.). Кроме того, он медленно и трудно отлаживать.

Второй вариант выполняется быстрее и имеет меньше проблем, но в конечном итоге легко получить заглушки, которые ведут себя по-другому, чем реальность во времени, из-за возможных изменений кода. Таким образом, при развертывании в prod возможно успешные тесты, но неудачное приложение.

Лучшим решением будет использование проверки контракта на основе потребителя, так что вы убедитесь, что API-сервисы поставщика совместимы с потребительскими вызовами. С этой целью разработчики Spring могут использовать Spring Cloud Contract. В других средах существует структура под названием PACT. Оба могут использоваться и с клиентами Fign. Здесь приведен пример с PACT.