diff --git a/spring-cloud/spring-cloud-eureka/pom.xml b/spring-cloud/spring-cloud-eureka/pom.xml index 7af0c15352..9d7350e774 100644 --- a/spring-cloud/spring-cloud-eureka/pom.xml +++ b/spring-cloud/spring-cloud-eureka/pom.xml @@ -19,6 +19,7 @@ spring-cloud-eureka-server spring-cloud-eureka-client spring-cloud-eureka-feign-client + spring-cloud-eureka-feign-client-integration-test diff --git a/spring-cloud/spring-cloud-eureka/spring-cloud-eureka-feign-client-integration-test/pom.xml b/spring-cloud/spring-cloud-eureka/spring-cloud-eureka-feign-client-integration-test/pom.xml new file mode 100644 index 0000000000..3348dbb24f --- /dev/null +++ b/spring-cloud/spring-cloud-eureka/spring-cloud-eureka-feign-client-integration-test/pom.xml @@ -0,0 +1,97 @@ + + + 4.0.0 + spring-cloud-eureka-feign-client-integration-test + 1.0.0-SNAPSHOT + spring-cloud-eureka-feign-client-integration-test + jar + Spring Cloud Eureka - Feign Client Integration Tests + + + com.baeldung.spring.cloud + spring-cloud-eureka + 1.0.0-SNAPSHOT + + + + + + org.junit + junit-bom + ${junit-jupiter.version} + pom + import + + + org.springframework.cloud + spring-cloud-starter-parent + ${spring-cloud-dependencies.version} + pom + import + + + + + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + + org.springframework.cloud + spring-cloud-starter-netflix-ribbon + + + + org.springframework.cloud + spring-cloud-starter-netflix-eureka-client + + + + org.springframework.boot + spring-boot-starter-web + + + + com.github.tomakehurst + wiremock + 2.27.2 + test + + + + org.projectlombok + lombok + + + + org.testcontainers + testcontainers + 1.14.3 + test + + + + org.awaitility + awaitility + 4.0.3 + test + + + + + + + + maven-surefire-plugin + + 1 + true + + + + + + diff --git a/spring-cloud/spring-cloud-eureka/spring-cloud-eureka-feign-client-integration-test/src/main/java/com/baeldung/spring/cloud/Application.java b/spring-cloud/spring-cloud-eureka/spring-cloud-eureka-feign-client-integration-test/src/main/java/com/baeldung/spring/cloud/Application.java new file mode 100644 index 0000000000..342e7e163b --- /dev/null +++ b/spring-cloud/spring-cloud-eureka/spring-cloud-eureka-feign-client-integration-test/src/main/java/com/baeldung/spring/cloud/Application.java @@ -0,0 +1,15 @@ +package com.baeldung.spring.cloud; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.openfeign.EnableFeignClients; + +@EnableFeignClients +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + +} diff --git a/spring-cloud/spring-cloud-eureka/spring-cloud-eureka-feign-client-integration-test/src/main/java/com/baeldung/spring/cloud/client/BooksClient.java b/spring-cloud/spring-cloud-eureka/spring-cloud-eureka-feign-client-integration-test/src/main/java/com/baeldung/spring/cloud/client/BooksClient.java new file mode 100644 index 0000000000..a263624b28 --- /dev/null +++ b/spring-cloud/spring-cloud-eureka/spring-cloud-eureka-feign-client-integration-test/src/main/java/com/baeldung/spring/cloud/client/BooksClient.java @@ -0,0 +1,16 @@ +package com.baeldung.spring.cloud.client; + +import com.baeldung.spring.cloud.model.Book; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.RequestMapping; + +import java.util.List; + +@FeignClient("books-service") +//@FeignClient(value="simple-books-client", url="${book.service.url}") +public interface BooksClient { + + @RequestMapping("/books") + List getBooks(); + +} diff --git a/spring-cloud/spring-cloud-eureka/spring-cloud-eureka-feign-client-integration-test/src/main/java/com/baeldung/spring/cloud/model/Book.java b/spring-cloud/spring-cloud-eureka/spring-cloud-eureka-feign-client-integration-test/src/main/java/com/baeldung/spring/cloud/model/Book.java new file mode 100644 index 0000000000..64492f678d --- /dev/null +++ b/spring-cloud/spring-cloud-eureka/spring-cloud-eureka-feign-client-integration-test/src/main/java/com/baeldung/spring/cloud/model/Book.java @@ -0,0 +1,15 @@ +package com.baeldung.spring.cloud.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class Book { + + private String title; + private String author; + +} diff --git a/spring-cloud/spring-cloud-eureka/spring-cloud-eureka-feign-client-integration-test/src/test/java/com/baeldung/spring/cloud/client/BookMocks.java b/spring-cloud/spring-cloud-eureka/spring-cloud-eureka-feign-client-integration-test/src/test/java/com/baeldung/spring/cloud/client/BookMocks.java new file mode 100644 index 0000000000..2cce72e6cb --- /dev/null +++ b/spring-cloud/spring-cloud-eureka/spring-cloud-eureka-feign-client-integration-test/src/test/java/com/baeldung/spring/cloud/client/BookMocks.java @@ -0,0 +1,27 @@ +package com.baeldung.spring.cloud.client; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; + +import java.io.IOException; + +import static java.nio.charset.Charset.defaultCharset; +import static org.springframework.util.StreamUtils.copyToString; + +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())))); + } + +} diff --git a/spring-cloud/spring-cloud-eureka/spring-cloud-eureka-feign-client-integration-test/src/test/java/com/baeldung/spring/cloud/client/BooksClientIntegrationTest.java b/spring-cloud/spring-cloud-eureka/spring-cloud-eureka-feign-client-integration-test/src/test/java/com/baeldung/spring/cloud/client/BooksClientIntegrationTest.java new file mode 100644 index 0000000000..2842eef435 --- /dev/null +++ b/spring-cloud/spring-cloud-eureka/spring-cloud-eureka-feign-client-integration-test/src/test/java/com/baeldung/spring/cloud/client/BooksClientIntegrationTest.java @@ -0,0 +1,53 @@ +package com.baeldung.spring.cloud.client; + +import com.baeldung.spring.cloud.model.Book; +import com.github.tomakehurst.wiremock.WireMockServer; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.io.IOException; + +import static com.baeldung.spring.cloud.client.BookMocks.setupMockBooksResponse; +import static java.util.Arrays.asList; +import static org.junit.Assert.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@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 { + setupMockBooksResponse(mockBooksService); + } + + @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")))); + } + +} diff --git a/spring-cloud/spring-cloud-eureka/spring-cloud-eureka-feign-client-integration-test/src/test/java/com/baeldung/spring/cloud/client/EurekaContainerConfig.java b/spring-cloud/spring-cloud-eureka/spring-cloud-eureka-feign-client-integration-test/src/test/java/com/baeldung/spring/cloud/client/EurekaContainerConfig.java new file mode 100644 index 0000000000..6747d14b88 --- /dev/null +++ b/spring-cloud/spring-cloud-eureka/spring-cloud-eureka-feign-client-integration-test/src/test/java/com/baeldung/spring/cloud/client/EurekaContainerConfig.java @@ -0,0 +1,39 @@ +package com.baeldung.spring.cloud.client; + +import org.jetbrains.annotations.NotNull; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.test.util.TestPropertyValues; +import org.springframework.context.ApplicationContextInitializer; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.test.context.ActiveProfiles; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.lifecycle.Startables; + +import java.util.stream.Stream; + +@TestConfiguration +@ActiveProfiles("eureka-test") +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); + + } + + } + +} diff --git a/spring-cloud/spring-cloud-eureka/spring-cloud-eureka-feign-client-integration-test/src/test/java/com/baeldung/spring/cloud/client/LoadBalancerBooksClientIntegrationTest.java b/spring-cloud/spring-cloud-eureka/spring-cloud-eureka-feign-client-integration-test/src/test/java/com/baeldung/spring/cloud/client/LoadBalancerBooksClientIntegrationTest.java new file mode 100644 index 0000000000..f05df11ba3 --- /dev/null +++ b/spring-cloud/spring-cloud-eureka/spring-cloud-eureka-feign-client-integration-test/src/test/java/com/baeldung/spring/cloud/client/LoadBalancerBooksClientIntegrationTest.java @@ -0,0 +1,65 @@ +package com.baeldung.spring.cloud.client; + +import com.baeldung.spring.cloud.model.Book; +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.io.IOException; + +import static com.baeldung.spring.cloud.client.BookMocks.setupMockBooksResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; +import static com.github.tomakehurst.wiremock.client.WireMock.moreThan; +import static java.util.Arrays.asList; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@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")))); + } +} diff --git a/spring-cloud/spring-cloud-eureka/spring-cloud-eureka-feign-client-integration-test/src/test/java/com/baeldung/spring/cloud/client/MockBookServiceConfig.java b/spring-cloud/spring-cloud-eureka/spring-cloud-eureka-feign-client-integration-test/src/test/java/com/baeldung/spring/cloud/client/MockBookServiceConfig.java new file mode 100644 index 0000000000..1fff2ec9c0 --- /dev/null +++ b/spring-cloud/spring-cloud-eureka/spring-cloud-eureka-feign-client-integration-test/src/test/java/com/baeldung/spring/cloud/client/MockBookServiceConfig.java @@ -0,0 +1,22 @@ +package com.baeldung.spring.cloud.client; + +import com.baeldung.spring.cloud.model.Book; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Collections; +import java.util.List; + +@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")); + } + +} diff --git a/spring-cloud/spring-cloud-eureka/spring-cloud-eureka-feign-client-integration-test/src/test/java/com/baeldung/spring/cloud/client/RibbonTestConfig.java b/spring-cloud/spring-cloud-eureka/spring-cloud-eureka-feign-client-integration-test/src/test/java/com/baeldung/spring/cloud/client/RibbonTestConfig.java new file mode 100644 index 0000000000..273ba182b1 --- /dev/null +++ b/spring-cloud/spring-cloud-eureka/spring-cloud-eureka-feign-client-integration-test/src/test/java/com/baeldung/spring/cloud/client/RibbonTestConfig.java @@ -0,0 +1,41 @@ +package com.baeldung.spring.cloud.client; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.netflix.loadbalancer.Server; +import com.netflix.loadbalancer.ServerList; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.cloud.netflix.ribbon.StaticServerList; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.ActiveProfiles; + +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; + +@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())); + } + +} diff --git a/spring-cloud/spring-cloud-eureka/spring-cloud-eureka-feign-client-integration-test/src/test/java/com/baeldung/spring/cloud/client/ServiceDiscoveryBooksClientIntegrationTest.java b/spring-cloud/spring-cloud-eureka/spring-cloud-eureka-feign-client-integration-test/src/test/java/com/baeldung/spring/cloud/client/ServiceDiscoveryBooksClientIntegrationTest.java new file mode 100644 index 0000000000..027579d20d --- /dev/null +++ b/spring-cloud/spring-cloud-eureka/spring-cloud-eureka-feign-client-integration-test/src/test/java/com/baeldung/spring/cloud/client/ServiceDiscoveryBooksClientIntegrationTest.java @@ -0,0 +1,52 @@ +package com.baeldung.spring.cloud.client; + +import com.baeldung.spring.cloud.Application; +import com.baeldung.spring.cloud.model.Book; +import com.netflix.discovery.EurekaClient; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Lazy; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.util.List; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.awaitility.Awaitility.await; +import static org.junit.jupiter.api.Assertions.assertEquals; + +@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()); + } + +} diff --git a/spring-cloud/spring-cloud-eureka/spring-cloud-eureka-feign-client-integration-test/src/test/java/com/baeldung/spring/cloud/client/WireMockConfig.java b/spring-cloud/spring-cloud-eureka/spring-cloud-eureka-feign-client-integration-test/src/test/java/com/baeldung/spring/cloud/client/WireMockConfig.java new file mode 100644 index 0000000000..82b7cddede --- /dev/null +++ b/spring-cloud/spring-cloud-eureka/spring-cloud-eureka-feign-client-integration-test/src/test/java/com/baeldung/spring/cloud/client/WireMockConfig.java @@ -0,0 +1,17 @@ +package com.baeldung.spring.cloud.client; + +import com.github.tomakehurst.wiremock.WireMockServer; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.ActiveProfiles; + +@TestConfiguration +@ActiveProfiles("test") +public class WireMockConfig { + + @Bean(initMethod = "start", destroyMethod = "stop") + public WireMockServer mockBooksService() { + return new WireMockServer(9561); + } + +} diff --git a/spring-cloud/spring-cloud-eureka/spring-cloud-eureka-feign-client-integration-test/src/test/resources/application-eureka-test.yml b/spring-cloud/spring-cloud-eureka/spring-cloud-eureka-feign-client-integration-test/src/test/resources/application-eureka-test.yml new file mode 100644 index 0000000000..6f6af6a080 --- /dev/null +++ b/spring-cloud/spring-cloud-eureka/spring-cloud-eureka-feign-client-integration-test/src/test/resources/application-eureka-test.yml @@ -0,0 +1,3 @@ +spring: + application: + name: books-service diff --git a/spring-cloud/spring-cloud-eureka/spring-cloud-eureka-feign-client-integration-test/src/test/resources/application-ribbon-test.yml b/spring-cloud/spring-cloud-eureka/spring-cloud-eureka-feign-client-integration-test/src/test/resources/application-ribbon-test.yml new file mode 100644 index 0000000000..84a78d0ec7 --- /dev/null +++ b/spring-cloud/spring-cloud-eureka/spring-cloud-eureka-feign-client-integration-test/src/test/resources/application-ribbon-test.yml @@ -0,0 +1,3 @@ +eureka: + client: + enabled: false diff --git a/spring-cloud/spring-cloud-eureka/spring-cloud-eureka-feign-client-integration-test/src/test/resources/application-test.yml b/spring-cloud/spring-cloud-eureka/spring-cloud-eureka-feign-client-integration-test/src/test/resources/application-test.yml new file mode 100644 index 0000000000..dce11adf69 --- /dev/null +++ b/spring-cloud/spring-cloud-eureka/spring-cloud-eureka-feign-client-integration-test/src/test/resources/application-test.yml @@ -0,0 +1,11 @@ +#book: +# service: +# url: http://localhost:9561 + +books-service: + ribbon: + listOfServers: http://localhost:9561 + +eureka: + client: + enabled: false diff --git a/spring-cloud/spring-cloud-eureka/spring-cloud-eureka-feign-client-integration-test/src/test/resources/payload/get-books-response.json b/spring-cloud/spring-cloud-eureka/spring-cloud-eureka-feign-client-integration-test/src/test/resources/payload/get-books-response.json new file mode 100644 index 0000000000..b4223ff8f2 --- /dev/null +++ b/spring-cloud/spring-cloud-eureka/spring-cloud-eureka-feign-client-integration-test/src/test/resources/payload/get-books-response.json @@ -0,0 +1,10 @@ +[ + { + "title": "Dune", + "author": "Frank Herbert" + }, + { + "title": "Foundation", + "author": "Isaac Asimov" + } +]