與Spring Cloud Netflix和Feign的集成測試

    1.概述

    在本文中,我們將探討Feign Client集成測試

    我們將創建一個基本的Open Feign Client,並在WireMock的幫助下為其編寫一個簡單的集成測試

    之後,我們將向客戶添加功能區配置,並為其構建集成測試。最後,我們將配置一個Eureka測試容器並測試此設置,以確保我們的整個配置都能按預期工作。

    2.Feign Client

    要設置Feign Client,我們應該首先添加Spring Cloud OpenFeign Maven依賴項:

    <dependency>
    
     <groupId>org.springframework.cloud</groupId>
    
     <artifactId>spring-cloud-starter-openfeign</artifactId>
    
     </dependency>
    

    之後,讓我們為模型創建一個Book類:

    public class Book {
    
     private String title;
    
     private String author;
    
     }

    最後,讓我們創建Feign Client界面:

    @FeignClient(value="simple-books-client", url="${book.service.url}")
    
     public interface BooksClient {
    
    
    
     @RequestMapping("/books")
    
     List<Book> getBooks();
    
    
    
     }

    現在,我們有了一個Feign客戶端,該客戶端從REST服務檢索Books列表。現在,讓我們繼續前進並編寫一些集成測試。

    3. WireMock

    3.1。設置WireMock服務器

    如果要測試BooksClient,則需要提供/books端點的模擬服務。我們的客戶將對此模擬服務進行呼叫。為此,我們將使用WireMock。

    因此,讓我們添加WireMock Maven依賴項:

    <dependency>
    
     <groupId>com.github.tomakehurst</groupId>
    
     <artifactId>wiremock</artifactId>
    
     <scope>test</scope>
    
     </dependency>
    

    並配置模擬服務器:

    @TestConfiguration
    
     public class WireMockConfig {
    
    
    
     @Autowired
    
     private WireMockServer wireMockServer;
    
    
    
     @Bean(initMethod = "start", destroyMethod = "stop")
    
     public WireMockServer mockBooksService() {
    
     return new WireMockServer(9561);
    
     }
    
    
    
     }

    現在,我們有一個正在運行的模擬服務器,它在端口9651上接受連接。

    3.2。設置模擬

    讓我們將屬性book.service.url添加到指向WireMockServer端口的application-test.yml

    book:
    
     service:
    
     url: http://localhost:9561

    我們還為/books端點準備一個模擬響應get-books-response.json

    [
    
     {
    
     "title": "Dune",
    
     "author": "Frank Herbert"
    
     },
    
     {
    
     "title": "Foundation",
    
     "author": "Isaac Asimov"
    
     }
    
     ]

    現在,讓我們在/books端點上為GET請求配置模擬響應:

    public class BookMocks {
    
    
    
     public static void setupMockBooksResponse(WireMockServer mockService) throws IOException {
    
     mockService.stubFor(WireMock.get(WireMock.urlEqualTo("/books"))
    
     .willReturn(WireMock.aResponse()
    
     .withStatus(HttpStatus.OK.value())
    
     .withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE)
    
     .withBody(
    
     copyToString(
    
     BookMocks.class.getClassLoader().getResourceAsStream("payload/get-books-response.json"),
    
     defaultCharset()))));
    
     }
    
    
    
     }

    至此,所有必需的配置均已就緒。讓我們繼續寫我們的第一個測試。

    4.我們的首次集成測試

    讓我們創建一個集成測試BooksClientIntegrationTest

    @SpringBootTest
    
     @ActiveProfiles("test")
    
     @EnableConfigurationProperties
    
     @ExtendWith(SpringExtension.class)
    
     @ContextConfiguration(classes = { WireMockConfig.class })
    
     class BooksClientIntegrationTest {
    
    
    
     @Autowired
    
     private WireMockServer mockBooksService;
    
    
    
     @Autowired
    
     private BooksClient booksClient;
    
    
    
     @BeforeEach
    
     void setUp() throws IOException {
    
     BookMocks.setupMockBooksResponse(mockBooksService);
    
     }
    
    
    
     // ...
    
     }

    至此,我們已經為SpringBootTest配置了WireMockServer準備在BooksClient調用/books端點時返回預定的Books列表。

    最後,讓我們添加測試方法:

    @Test
    
     public void whenGetBooks_thenBooksShouldBeReturned() {
    
     assertFalse(booksClient.getBooks().isEmpty());
    
     }
    
    
    
     @Test
    
     public void whenGetBooks_thenTheCorrectBooksShouldBeReturned() {
    
     assertTrue(booksClient.getBooks()
    
     .containsAll(asList(
    
     new Book("Dune", "Frank Herbert"),
    
     new Book("Foundation", "Isaac Asimov"))));
    
     }

    5.與負載均衡集成

    現在,通過添加Ribbon提供的負載平衡功能來改進我們的客戶端

    我們在客戶端界面上需要做的就是刪除硬編碼的服務URL,而是通過服務名稱book-service引用該book-service

    @FeignClient("books-service")
    
     public interface BooksClient {
    
     ...

    接下來,添加Netflix Ribbon Maven依賴項:

    <dependency>
    
     <groupId>org.springframework.cloud</groupId>
    
     <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
    
     </dependency>

    最後,在application-test.yml文件中,我們現在應該刪除book.service.url ,而是定義功能區listOfServers

    books-service:
    
     ribbon:
    
     listOfServers: http://localhost:9561

    現在,讓我們再次運行BooksClientIntegrationTest 。它應該通過,確認新設置可以正常工作。

    5.1。動態端口配置

    如果我們不想對服務器的端口進行硬編碼,則可以將WireMock配置為在啟動時使用動態端口。

    為此,讓我們創建另一個測試配置RibbonTestConfig:

    @TestConfiguration
    
     @ActiveProfiles("ribbon-test")
    
     public class RibbonTestConfig {
    
    
    
     @Autowired
    
     private WireMockServer mockBooksService;
    
    
    
     @Autowired
    
     private WireMockServer secondMockBooksService;
    
    
    
     @Bean(initMethod = "start", destroyMethod = "stop")
    
     public WireMockServer mockBooksService() {
    
     return new WireMockServer(options().dynamicPort());
    
     }
    
    
    
     @Bean(name="secondMockBooksService", initMethod = "start", destroyMethod = "stop")
    
     public WireMockServer secondBooksMockService() {
    
     return new WireMockServer(options().dynamicPort());
    
     }
    
    
    
     @Bean
    
     public ServerList ribbonServerList() {
    
     return new StaticServerList<>(
    
     new Server("localhost", mockBooksService.port()),
    
     new Server("localhost", secondMockBooksService.port()));
    
     }
    
    
    
     }

    此配置設置了兩個WireMock服務器,每個服務器運行在運行時動態分配的不同端口上。此外,它還使用兩個模擬服務器配置功能區服務器列表。

    5.2。負載平衡測試

    現在我們已經配置了Ribbon負載平衡器,讓我們確保我們的BooksClient在兩個模擬服務器之間正確交替:

    @SpringBootTest
    
     @ActiveProfiles("ribbon-test")
    
     @EnableConfigurationProperties
    
     @ExtendWith(SpringExtension.class)
    
     @ContextConfiguration(classes = { RibbonTestConfig.class })
    
     class LoadBalancerBooksClientIntegrationTest {
    
    
    
     @Autowired
    
     private WireMockServer mockBooksService;
    
    
    
     @Autowired
    
     private WireMockServer secondMockBooksService;
    
    
    
     @Autowired
    
     private BooksClient booksClient;
    
    
    
     @BeforeEach
    
     void setUp() throws IOException {
    
     setupMockBooksResponse(mockBooksService);
    
     setupMockBooksResponse(secondMockBooksService);
    
     }
    
    
    
     @Test
    
     void whenGetBooks_thenRequestsAreLoadBalanced() {
    
     for (int k = 0; k < 10; k++) {
    
     booksClient.getBooks();
    
     }
    
    
    
     mockBooksService.verify(
    
     moreThan(0), getRequestedFor(WireMock.urlEqualTo("/books")));
    
     secondMockBooksService.verify(
    
     moreThan(0), getRequestedFor(WireMock.urlEqualTo("/books")));
    
     }
    
    
    
     @Test
    
     public void whenGetBooks_thenTheCorrectBooksShouldBeReturned() {
    
     assertTrue(booksClient.getBooks()
    
     .containsAll(asList(
    
     new Book("Dune", "Frank Herbert"),
    
     new Book("Foundation", "Isaac Asimov"))));
    
     }
    
     }

    6.Eureka整合

    到目前為止,我們已經看到瞭如何測試使用Ribbon進行負載平衡的客戶端。但是,如果我們的設置使用諸如Eureka之類的服務發現系統,該怎麼辦?我們應該編寫一個集成測試,以確保我們的BooksClient**在這種情況下也能按預期工作**。

    為此,我們將運行Eureka服務器作為測試容器。然後,我們啟動並在我們的Eureka容器中註冊一個模擬book-service 。最後,一旦安裝完成,我們就可以對其進行測試。

    在繼續之前,讓我們添加TestcontainersNetflix Eureka Client Maven依賴項:

    <dependency>
    
     <groupId>org.springframework.cloud</groupId>
    
     <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    
     </dependency>
    
     <dependency>
    
     <groupId>org.testcontainers</groupId>
    
     <artifactId>testcontainers</artifactId>
    
     <scope>test</scope>
    
     </dependency>

    6.1。 TestContainer設置

    讓我們創建一個TestContainer配置,以啟動我們的Eureka服務器:

    public class EurekaContainerConfig {
    
    
    
     public static class Initializer implements ApplicationContextInitializer {
    
    
    
     public static GenericContainer eurekaServer =
    
     new GenericContainer("springcloud/eureka").withExposedPorts(8761);
    
    
    
     @Override
    
     public void initialize(@NotNull ConfigurableApplicationContext configurableApplicationContext) {
    
    
    
     Startables.deepStart(Stream.of(eurekaServer)).join();
    
    
    
     TestPropertyValues
    
     .of("eureka.client.serviceUrl.defaultZone=http://localhost:"
    
     + eurekaServer.getFirstMappedPort().toString()
    
     + "/eureka")
    
     .applyTo(configurableApplicationContext);
    
     }
    
     }
    
     }

    如我們所見,上面的初始化程序啟動了容器。然後,它暴露Eureka服務器正在監聽的端口8761。

    最後,在Eureka服務啟動之後,我們需要更新eureka.client.serviceUrl.defaultZone屬性。這定義了用於服務發現的Eureka服務器的地址。

    6.2。註冊模擬服務器

    現在,我們的Eureka服務器已啟動並正在運行,我們需要註冊一個模擬books-service 。我們通過簡單地創建一個RestController來做到這一點:

    @Configuration
    
     @RestController
    
     @ActiveProfiles("eureka-test")
    
     public class MockBookServiceConfig {
    
    
    
     @RequestMapping("/books")
    
     public List getBooks() {
    
     return Collections.singletonList(new Book("Hitchhiker's Guide to the Galaxy", "Douglas Adams"));
    
     }
    
     }

    為了註冊此控制器,我們現在要做的就是確保我們的application-eureka-test.ymlspring.application.name屬性是books-service,BooksClient接口中使用的服務名稱相同。

    注意:現在netflix-eureka-client庫位於我們的依賴項列表中,默認情況下將使用Eureka進行服務發現。因此,如果我們希望不使用Eureka的以前的測試保持通過,則需要手動將eureka.client.enabled設置為false 。這樣,即使庫位於路徑上, BooksClient也不會嘗試使用Eureka來定位服務,而是使用Ribbon配置。

    6.3。整合測試

    再一次,我們擁有所有需要的配置塊,因此讓我們將它們全部組合在一起進行測試:

    @ActiveProfiles("eureka-test")
    
     @EnableConfigurationProperties
    
     @ExtendWith(SpringExtension.class)
    
     @SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
    
     @ContextConfiguration(classes = { MockBookServiceConfig.class },
    
     initializers = { EurekaContainerConfig.Initializer.class })
    
     class ServiceDiscoveryBooksClientIntegrationTest {
    
    
    
     @Autowired
    
     private BooksClient booksClient;
    
    
    
     @Lazy
    
     @Autowired
    
     private EurekaClient eurekaClient;
    
    
    
     @BeforeEach
    
     void setUp() {
    
     await().atMost(60, SECONDS).until(() -> eurekaClient.getApplications().size() > 0);
    
     }
    
    
    
     @Test
    
     public void whenGetBooks_thenTheCorrectBooksAreReturned() {
    
     List books = booksClient.getBooks();
    
    
    
     assertEquals(1, books.size());
    
     assertEquals(
    
     new Book("Hitchhiker's guide to the galaxy", "Douglas Adams"),
    
     books.stream().findFirst().get());
    
     }
    
    
    
     }

    此測試中發生了一些事情。讓我們一一看一下。

    首先, EurekaContainerConfig內部的上下文初始化EurekaContainerConfig啟動Eureka服務。

    然後, SpringBootTest啟動books-service應用程序,該應用程序公開MockBookServiceConfig定義的控制器。

    因為Eureka容器和Web應用程序的啟動可能需要幾秒鐘的時間,所以我們需要等到books-service被註冊。這發生在setUp測試。

    最後,測試方法可以驗證BooksClient與Eureka配置結合使用是否正確。

    7.結論

    在本文中,我們探討了為Spring Cloud Feign Client編寫集成測試的不同方法。我們從一個基本的客戶開始,我們在WireMock的幫助下進行了測試。之後,我們繼續使用Ribbon添加負載平衡。我們編寫了一個集成測試,並確保我們的Feign Client與Ribbon所提供的客戶端負載平衡正常工作。最後,我們將Eureka服務發現添加到了組合中。再一次,我們確保我們的客戶仍然可以按預期工作。