1. 개요

이 기사에서는 Feign Client통합 테스트 를 살펴볼 것 입니다.

우리는 기본 만듭니다 열기 척하기 클라이언트 있는 우리는 간단한 통합 테스트 쓸 것이다 의 도움으로 WireMock을 .

그런 다음 클라이언트에 리본 구성을 추가하고 이에 대한 통합 테스트도 빌드합니다. 마지막으로 Eureka 테스트 컨테이너를 구성 하고이 설정테스트하여 전체 구성이 예상대로 작동하는지 확인합니다.

2. Feign 클라이언트

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();

}

이제 REST 서비스에서 도서 List을 검색하는 Feign Client가 있습니다. 이제 앞으로 나아가서 몇 가지 통합 테스트를 작성해 보겠습니다.

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. Mock 설정

의이 속성 추가하자 book.service.url을  우리에게 응용 프로그램 test.yml의 받는 사람을 가리키는 WireMockServer의 포트 :

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(mockBooksSerfvice);
    }

    // ...
}

이 시점에서, 우리는이 SpringBootTest가 로 구성 WireMockServer 의 미리 정의 된 List을 반환 할 준비가 / 책 에 의해 호출 엔드 포인트 BooksClient .

마지막으로 테스트 방법을 추가해 보겠습니다.

@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로 서비스를 참조하는 것입니다 .

@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을 제거 하고 대신 Ribbon 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 서버를 설정하며, 각각은 런타임에 동적으로 할당 된 다른 포트에서 실행됩니다. 또한 두 개의 모의 서버로 리본 서버 List을 구성합니다.

5.2. 부하 분산 테스트

이제 리본로드 밸런서가 구성 되었으므로 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와 같은 서비스 검색 시스템을 사용한다면 어떨까요? 이러한 컨텍스트에서도 BooksClient 가 예상대로 작동 하는지 확인하는 통합 테스트를 작성해야합니다 .

이를 위해 Eureka 서버를 테스트 컨테이너로 실행합니다 . 그런 다음 Eureka 컨테이너를 사용 하여 모의 도서 서비스시작하고 등록합니다 . 마지막으로이 설치가 완료되면 테스트를 실행할 수 있습니다.

계속 진행하기 전에 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 설정

Eureka 서버를 가동 할 TestContainer 구성을 만들어 보겠습니다.

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 서버가 가동되고 실행 중이므로 mock 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"));
    }
}

우리가이 컨트롤러를 등록하기 위해 지금해야 할 일은, 있는지 확인하는 것입니다 spring.application.name의 우리의 재산 응용 프로그램 - 유레카 - test.yml이 있습니다 책 서비스, 에 사용되는 서비스 이름과 같은 BooksClient의 인터페이스 .

참고 : 이제 netflix-eureka-client 라이브러리가 의존성 List에 있으므로 서비스 검색에 기본적으로 Eureka가 사용됩니다. 따라서 Eureka를 사용하지 않는 이전 테스트를 계속 통과 하려면 eureka.client.enabled를 false 로 수동 설정해야합니다 . 이렇게하면 라이브러리가 경로에 있더라도 BooksClient 는 서비스를 찾기 위해 Eureka를 사용하지 않고 대신 리본 구성을 사용합니다.

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 내부의 컨텍스트 이니셜 라이저 가 Eureka 서비스를 시작합니다.

그런 다음 SpringBootTestMockBookServiceConfig에 정의 된 컨트롤러를 노출하는 책 서비스 애플리케이션을 시작합니다 .

때문에 유레카 컨테이너의 시작 및 웹 응용 프로그램이 몇 초 정도 걸릴 수 있습니다 , 우리는 때까지 기다릴 필요가 책 서비스가 등록됩니다. 이것은에서 일어나는 의 setUp 시험.

마지막으로 테스트 방법은 BooksClient가 실제로 Eureka 구성과 함께 올바르게 작동하는지 확인합니다.

7. 결론

이 기사에서는 Spring Cloud Feign Client에 대한 통합 테스트를 작성할 수있는 다양한 방법을 살펴 보았습니다 . 우리는 WireMock의 도움으로 테스트 한 기본 클라이언트로 시작했습니다. 그 후 리본으로로드 밸런싱을 추가했습니다. 통합 테스트를 작성하고 Feign Client가 Ribbon에서 제공하는 클라이언트 측 부하 분산과 올바르게 작동하는지 확인했습니다. 마지막으로 유레카 서비스 검색을 믹스에 추가했습니다. 그리고 다시 우리는 클라이언트가 예상대로 작동하는지 확인했습니다.

항상 그렇듯이 전체 코드는 GitHub에서 사용할 수 있습니다 .