2016-09-13 5 views
14

Я использую Spring-Cloud-Netflix для связи между микросервисами. Предположим, у меня есть две службы - Foo и Bar, а Foo потребляет одну из конечных точек REST Bar. Я использую интерфейс с аннотацией @FeignClient:Как написать интеграционные тесты с spring-cloud-netflix и feign

@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 в эврика, так что симулировать способен найти и общаться с ним? Какая магия аннотаций мне нужна?

+0

Я попытался предложить вам ответ, но я понимаю, что, скорее всего, ваша цель не очень хорошая. Если вы говорите об интеграционных тестах, вам не нужно издеваться над логикой «BarClient». если вы это сделаете, тогда ваш тест будет Unit Test, а не интеграцией. И если это модульный тест, вы можете издеваться над «BarClient» простым с Mokito, без HTTP-запросов вообще. Я не понимаю, зачем вам нужен HTTP-запрос? –

+2

Я не хочу строить интеграционные тесты, которые объединяют несколько микросервисов. Когда я говорю об интеграционном тесте, я имею в виду тестирование интеграции всех технических уровней в «FooService», в отличие от единичных тестов, которые проверяют только один класс, и заменяют остальных на mocks или stub. –

+0

Вы просмотрели [RestClientTest] (http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-testing-spring-boot-applications-testing-autoconfigured- rest-client), а это «MockRestServiceServer» в Spring Boot 1.4? – Tim

ответ

1

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

@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 чем симулировать.

+0

Хм, нижний ни одного комментария? WTF.Скажите мне, что не так в этом ответе –

+0

Но это не использует Feign, конечно, это действительно, если вы используете RestTemplate, но вопрос спрашивает о Feign with SpringBoot. Люди используют Feign, потому что это лучше, чем RestTemplate ... – JeeBee

0

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

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

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

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

+0

Привет, Сергей, за ваш ответ! Добавление реестра eureka к моему сервису звучит неплохо, но как я могу добавить к нему поддельный «BarService»? Учитывая ваше второе предложение, заменив «BarClient» на Mockito, да, я тоже хочу это сделать, но это для модульного теста. Я также хочу иметь интеграционный тест, который включает в себя настоящую волшебную магию. –

2

Вот пример, как выполнить проводку 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() 
      ); 
     } 
    } 
} 

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

+0

Эй, спасибо за ваш ответ! Включает ли это Эврика? Что такое google.url? –

+0

Привет! «google.url» - это произвольное имя свойства для хранения URL-адреса службы зависимых (главная страница google в этом примере). Эврика не принимается во внимание. Пришел к этому решению, пытаясь переопределить значение application.properties. – Alexander

3

Вот пример использования WireMock для тестирования конфигурации SpringBoot с помощью клиента Feign и 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(); 
    } 
} 

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

Для того, чтобы тесты на работу, нам нужно настроить ленты loadbalancer (будет использоваться внутренне Притвориться клиентом при отправке запроса 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())); 
     } 
    } 
} 

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

0

Там раньше в основном два варианта делать интеграционные тесты для microservices приложений:

  1. развертывания услуг в тестовой среде и сделать впритык ИСПЫТАНИЙ
  2. Дразнящий другие microservices

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

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

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

 Смежные вопросы

  • Нет связанных вопросов^_^