diff --git a/spring-cloud-modules/spring-cloud-openfeign/pom.xml b/spring-cloud-modules/spring-cloud-openfeign/pom.xml index a6a24c376e..0aa954691c 100644 --- a/spring-cloud-modules/spring-cloud-openfeign/pom.xml +++ b/spring-cloud-modules/spring-cloud-openfeign/pom.xml @@ -61,6 +61,12 @@ spring-boot-starter-test test + + com.github.tomakehurst + wiremock-jre8 + 2.33.2 + test + diff --git a/spring-cloud-modules/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/customizederrorhandling/client/ProductClient.java b/spring-cloud-modules/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/customizederrorhandling/client/ProductClient.java new file mode 100644 index 0000000000..8a6c5d5846 --- /dev/null +++ b/spring-cloud-modules/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/customizederrorhandling/client/ProductClient.java @@ -0,0 +1,17 @@ +package com.baeldung.cloud.openfeign.customizederrorhandling.client; + +import com.baeldung.cloud.openfeign.customizederrorhandling.config.FeignConfig; +import com.baeldung.cloud.openfeign.defaulterrorhandling.model.Product; + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +@FeignClient(name = "product-client-2", url = "http://localhost:8081/product/", configuration = FeignConfig.class) +public interface ProductClient { + + @RequestMapping(value = "{id}", method = RequestMethod.GET) + Product getProduct(@PathVariable(value = "id") String id); + +} \ No newline at end of file diff --git a/spring-cloud-modules/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/customizederrorhandling/config/CustomErrorDecoder.java b/spring-cloud-modules/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/customizederrorhandling/config/CustomErrorDecoder.java new file mode 100644 index 0000000000..3750e6288d --- /dev/null +++ b/spring-cloud-modules/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/customizederrorhandling/config/CustomErrorDecoder.java @@ -0,0 +1,24 @@ +package com.baeldung.cloud.openfeign.customizederrorhandling.config; + +import com.baeldung.cloud.openfeign.exception.BadRequestException; +import com.baeldung.cloud.openfeign.customizederrorhandling.exception.ProductNotFoundException; +import com.baeldung.cloud.openfeign.customizederrorhandling.exception.ProductServiceNotAvailableException; +import feign.Response; +import feign.codec.ErrorDecoder; + +public class CustomErrorDecoder implements ErrorDecoder { + + @Override + public Exception decode(String methodKey, Response response) { + switch (response.status()){ + case 400: + return new BadRequestException(); + case 404: + return new ProductNotFoundException("Product not found"); + case 503: + return new ProductServiceNotAvailableException("Product Api is unavailable"); + default: + return new Exception("Exception while getting product details"); + } + } +} diff --git a/spring-cloud-modules/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/customizederrorhandling/config/FeignConfig.java b/spring-cloud-modules/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/customizederrorhandling/config/FeignConfig.java new file mode 100644 index 0000000000..5cddd521f1 --- /dev/null +++ b/spring-cloud-modules/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/customizederrorhandling/config/FeignConfig.java @@ -0,0 +1,18 @@ +package com.baeldung.cloud.openfeign.customizederrorhandling.config; + +import feign.Logger; +import feign.codec.ErrorDecoder; +import org.springframework.context.annotation.Bean; + +public class FeignConfig { + + @Bean + Logger.Level feignLoggerLevel() { + return Logger.Level.FULL; + } + + @Bean + ErrorDecoder errorDecoder() { + return new CustomErrorDecoder(); + } +} \ No newline at end of file diff --git a/spring-cloud-modules/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/customizederrorhandling/controller/ProductController.java b/spring-cloud-modules/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/customizederrorhandling/controller/ProductController.java new file mode 100644 index 0000000000..9b7a5aea1d --- /dev/null +++ b/spring-cloud-modules/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/customizederrorhandling/controller/ProductController.java @@ -0,0 +1,27 @@ +package com.baeldung.cloud.openfeign.customizederrorhandling.controller; + +import com.baeldung.cloud.openfeign.customizederrorhandling.client.ProductClient; + + +import com.baeldung.cloud.openfeign.defaulterrorhandling.model.Product; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + + +@RestController("product_controller2") +@RequestMapping(value = "myapp2") +public class ProductController { + + private final ProductClient productClient; + + @Autowired + public ProductController(ProductClient productClient) { + this.productClient = productClient; + } + + @GetMapping("/product/{id}") + public Product getProduct(@PathVariable String id) { + return productClient.getProduct(id); + } + +} diff --git a/spring-cloud-modules/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/customizederrorhandling/exception/ErrorResponse.java b/spring-cloud-modules/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/customizederrorhandling/exception/ErrorResponse.java new file mode 100644 index 0000000000..c72e3db68b --- /dev/null +++ b/spring-cloud-modules/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/customizederrorhandling/exception/ErrorResponse.java @@ -0,0 +1,54 @@ +package com.baeldung.cloud.openfeign.customizederrorhandling.exception; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.springframework.http.HttpStatus; + +import java.util.Date; + +public class ErrorResponse { + + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy hh:mm:ss") + private Date timestamp; + + @JsonProperty(value = "code") + private int code; + + @JsonProperty(value = "status") + private String status; + @JsonProperty(value = "message") + private String message; + @JsonProperty(value = "details") + private String details; + + public ErrorResponse() { + } + + public ErrorResponse(HttpStatus httpStatus, String message, String details) { + timestamp = new Date(); + this.code = httpStatus.value(); + this.status = httpStatus.name(); + this.message = message; + this.details = details; + } + + public Date getTimestamp() { + return timestamp; + } + + public int getCode() { + return code; + } + + public String getStatus() { + return status; + } + + public String getMessage() { + return message; + } + + public String getDetails() { + return details; + } +} diff --git a/spring-cloud-modules/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/customizederrorhandling/exception/ProductExceptionHandler.java b/spring-cloud-modules/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/customizederrorhandling/exception/ProductExceptionHandler.java new file mode 100644 index 0000000000..2ed709bc34 --- /dev/null +++ b/spring-cloud-modules/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/customizederrorhandling/exception/ProductExceptionHandler.java @@ -0,0 +1,40 @@ +package com.baeldung.cloud.openfeign.customizederrorhandling.exception; + +import com.baeldung.cloud.openfeign.exception.BadRequestException; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.context.request.WebRequest; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + +@RestControllerAdvice +public class ProductExceptionHandler extends ResponseEntityExceptionHandler { + + @ExceptionHandler({ProductServiceNotAvailableException.class}) + public ResponseEntity handleProductServiceNotAvailableException(ProductServiceNotAvailableException exception, WebRequest request) { + return new ResponseEntity<>(new ErrorResponse( + HttpStatus.INTERNAL_SERVER_ERROR, + exception.getMessage(), + request.getDescription(false)), + HttpStatus.INTERNAL_SERVER_ERROR); + } + + @ExceptionHandler({ProductNotFoundException.class}) + public ResponseEntity handleProductNotFoundException(ProductNotFoundException exception, WebRequest request) { + return new ResponseEntity<>(new ErrorResponse( + HttpStatus.NOT_FOUND, + exception.getMessage(), + request.getDescription(false)), + HttpStatus.NOT_FOUND); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity handleGenericException(Exception exception, WebRequest request) { + return new ResponseEntity<>(new ErrorResponse( + HttpStatus.INTERNAL_SERVER_ERROR, + exception.getMessage(), + request.getDescription(false)), + HttpStatus.INTERNAL_SERVER_ERROR); + } +} diff --git a/spring-cloud-modules/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/customizederrorhandling/exception/ProductNotFoundException.java b/spring-cloud-modules/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/customizederrorhandling/exception/ProductNotFoundException.java new file mode 100644 index 0000000000..337cb89f7b --- /dev/null +++ b/spring-cloud-modules/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/customizederrorhandling/exception/ProductNotFoundException.java @@ -0,0 +1,8 @@ +package com.baeldung.cloud.openfeign.customizederrorhandling.exception; + +public class ProductNotFoundException extends RuntimeException { + + public ProductNotFoundException(String message) { + super(message); + } +} diff --git a/spring-cloud-modules/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/customizederrorhandling/exception/ProductServiceNotAvailableException.java b/spring-cloud-modules/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/customizederrorhandling/exception/ProductServiceNotAvailableException.java new file mode 100644 index 0000000000..ce30f8c310 --- /dev/null +++ b/spring-cloud-modules/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/customizederrorhandling/exception/ProductServiceNotAvailableException.java @@ -0,0 +1,8 @@ +package com.baeldung.cloud.openfeign.customizederrorhandling.exception; + +public class ProductServiceNotAvailableException extends RuntimeException { + + public ProductServiceNotAvailableException(String message) { + super(message); + } +} diff --git a/spring-cloud-modules/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/defaulterrorhandling/client/ProductClient.java b/spring-cloud-modules/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/defaulterrorhandling/client/ProductClient.java new file mode 100644 index 0000000000..b842b948d7 --- /dev/null +++ b/spring-cloud-modules/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/defaulterrorhandling/client/ProductClient.java @@ -0,0 +1,17 @@ +package com.baeldung.cloud.openfeign.defaulterrorhandling.client; + +import com.baeldung.cloud.openfeign.defaulterrorhandling.config.FeignConfig; +import com.baeldung.cloud.openfeign.defaulterrorhandling.model.Product; + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + +@FeignClient(name = "product-client", url = "http://localhost:8081/product/", configuration = FeignConfig.class) +public interface ProductClient { + + @RequestMapping(value = "{id}", method = RequestMethod.GET) + Product getProduct(@PathVariable(value = "id") String id); + +} \ No newline at end of file diff --git a/spring-cloud-modules/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/defaulterrorhandling/config/FeignConfig.java b/spring-cloud-modules/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/defaulterrorhandling/config/FeignConfig.java new file mode 100644 index 0000000000..f2329ebe0b --- /dev/null +++ b/spring-cloud-modules/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/defaulterrorhandling/config/FeignConfig.java @@ -0,0 +1,12 @@ +package com.baeldung.cloud.openfeign.defaulterrorhandling.config; + +import feign.Logger; +import org.springframework.context.annotation.Bean; + +public class FeignConfig { + + @Bean + Logger.Level feignLoggerLevel() { + return Logger.Level.FULL; + } +} \ No newline at end of file diff --git a/spring-cloud-modules/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/defaulterrorhandling/controller/ProductController.java b/spring-cloud-modules/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/defaulterrorhandling/controller/ProductController.java new file mode 100644 index 0000000000..df352f8d52 --- /dev/null +++ b/spring-cloud-modules/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/defaulterrorhandling/controller/ProductController.java @@ -0,0 +1,24 @@ +package com.baeldung.cloud.openfeign.defaulterrorhandling.controller; + +import com.baeldung.cloud.openfeign.defaulterrorhandling.client.ProductClient; + +import com.baeldung.cloud.openfeign.defaulterrorhandling.model.Product; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +@RestController("product_controller1") +@RequestMapping(value ="myapp1") +public class ProductController { + + private final ProductClient productClient; + + @Autowired + public ProductController(ProductClient productClient) { + this.productClient = productClient; + } + + @GetMapping("/product/{id}") + public Product getProduct(@PathVariable String id) { + return productClient.getProduct(id); + } +} diff --git a/spring-cloud-modules/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/defaulterrorhandling/model/Product.java b/spring-cloud-modules/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/defaulterrorhandling/model/Product.java new file mode 100644 index 0000000000..25a1662c99 --- /dev/null +++ b/spring-cloud-modules/spring-cloud-openfeign/src/main/java/com/baeldung/cloud/openfeign/defaulterrorhandling/model/Product.java @@ -0,0 +1,24 @@ +package com.baeldung.cloud.openfeign.defaulterrorhandling.model; + +public class Product { + + private String id; + + private String productName; + + private double price; + + + public String getId() { + return id; + } + + public String getProductName() { + return productName; + } + + public double getPrice() { + return price; + } + +} diff --git a/spring-cloud-modules/spring-cloud-openfeign/src/test/java/com/baeldung/cloud/openfeign/customizederrorhandling/client/ProductClientUnitTest.java b/spring-cloud-modules/spring-cloud-openfeign/src/test/java/com/baeldung/cloud/openfeign/customizederrorhandling/client/ProductClientUnitTest.java new file mode 100644 index 0000000000..7cf2a12692 --- /dev/null +++ b/spring-cloud-modules/spring-cloud-openfeign/src/test/java/com/baeldung/cloud/openfeign/customizederrorhandling/client/ProductClientUnitTest.java @@ -0,0 +1,57 @@ +package com.baeldung.cloud.openfeign.customizederrorhandling.client; + +import com.baeldung.cloud.openfeign.customizederrorhandling.exception.ProductNotFoundException; +import com.baeldung.cloud.openfeign.customizederrorhandling.exception.ProductServiceNotAvailableException; +import com.github.tomakehurst.wiremock.WireMockServer; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static org.junit.Assert.assertThrows; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ProductClientUnitTest { + + @Autowired + private ProductClient productClient; + + private WireMockServer wireMockServer; + + @Before + public void startWireMockServer() { + wireMockServer = new WireMockServer(8081); + configureFor("localhost", 8081); + wireMockServer.start(); + } + + @After + public void stopWireMockServer() { + wireMockServer.stop(); + } + + @Test + public void givenProductApiIsNotAvailable_whenGetProductCalled_thenThrowProductServiceNotAvailableException() { + String productId = "test"; + + stubFor(get(urlEqualTo("/product/" + productId)) + .willReturn(aResponse().withStatus(503))); + + assertThrows(ProductServiceNotAvailableException.class, () -> productClient.getProduct(productId)); + } + + @Test + public void givenProductNotFound_whenGetProductCalled_thenThrowBadRequestException() { + String productId = "test"; + + stubFor(get(urlEqualTo("/product/" + productId)) + .willReturn(aResponse().withStatus(404))); + + assertThrows(ProductNotFoundException.class, () -> productClient.getProduct(productId)); + } +} diff --git a/spring-cloud-modules/spring-cloud-openfeign/src/test/java/com/baeldung/cloud/openfeign/customizederrorhandling/controller/ProductControllerUnitTest.java b/spring-cloud-modules/spring-cloud-openfeign/src/test/java/com/baeldung/cloud/openfeign/customizederrorhandling/controller/ProductControllerUnitTest.java new file mode 100644 index 0000000000..cc9d029e07 --- /dev/null +++ b/spring-cloud-modules/spring-cloud-openfeign/src/test/java/com/baeldung/cloud/openfeign/customizederrorhandling/controller/ProductControllerUnitTest.java @@ -0,0 +1,98 @@ +package com.baeldung.cloud.openfeign.customizederrorhandling.controller; + +import com.baeldung.cloud.openfeign.customizederrorhandling.client.ProductClient; +import com.baeldung.cloud.openfeign.customizederrorhandling.exception.ErrorResponse; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.cloud.openfeign.FeignAutoConfiguration; +import org.springframework.http.HttpStatus; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static org.junit.Assert.assertEquals; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + + +@RunWith(SpringRunner.class) +@WebMvcTest(ProductController.class) +@ImportAutoConfiguration({FeignAutoConfiguration.class}) +public class ProductControllerUnitTest { + + @Autowired + private ProductClient productClient; + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + private WireMockServer wireMockServer; + + @Before + public void startWireMockServer() { + wireMockServer = new WireMockServer(8081); + configureFor("localhost", 8081); + wireMockServer.start(); + } + + @After + public void stopWireMockServer() { + wireMockServer.stop(); + } + + @Test + public void givenProductApiIsNotAvailable_whenGetProductCalled_ThenReturnInternalServerError() throws Exception { + String productId = "test"; + + stubFor(WireMock.get(urlEqualTo("/product/" + productId)) + .willReturn(aResponse() + .withStatus(HttpStatus.SERVICE_UNAVAILABLE.value()))); + + ErrorResponse expectedError = new ErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR, + "Product Api is unavailable","uri=/myapp2/product/" + productId); + + MvcResult result = mockMvc.perform(get("/myapp2/product/" + productId)) + .andExpect(status().isInternalServerError()).andReturn(); + + ErrorResponse errorResponse = objectMapper.readValue(result.getResponse().getContentAsString(), ErrorResponse.class); + + assertEquals(expectedError.getCode(), errorResponse.getCode()); + assertEquals(expectedError.getMessage(), errorResponse.getMessage()); + assertEquals(expectedError.getStatus(), errorResponse.getStatus()); + assertEquals(expectedError.getDetails(), errorResponse.getDetails()); + } + + @Test + public void givenProductIsNotFound_whenGetProductCalled_ThenReturnInternalServerError() throws Exception { + String productId = "test"; + + stubFor(WireMock.get(urlEqualTo("/product/" + productId)) + .willReturn(aResponse() + .withStatus(HttpStatus.NOT_FOUND.value()))); + + ErrorResponse expectedError = new ErrorResponse(HttpStatus.NOT_FOUND, + "Product not found","uri=/myapp2/product/" + productId); + + MvcResult result = mockMvc.perform(get("/myapp2/product/" + productId)) + .andExpect(status().isNotFound()).andReturn(); + + ErrorResponse errorResponse = objectMapper.readValue(result.getResponse().getContentAsString(), ErrorResponse.class); + + assertEquals(expectedError.getCode(), errorResponse.getCode()); + assertEquals(expectedError.getMessage(), errorResponse.getMessage()); + assertEquals(expectedError.getStatus(), errorResponse.getStatus()); + assertEquals(expectedError.getDetails(), errorResponse.getDetails()); + } +} diff --git a/spring-cloud-modules/spring-cloud-openfeign/src/test/java/com/baeldung/cloud/openfeign/defaulterrorhandling/client/ProductClientUnitTest.java b/spring-cloud-modules/spring-cloud-openfeign/src/test/java/com/baeldung/cloud/openfeign/defaulterrorhandling/client/ProductClientUnitTest.java new file mode 100644 index 0000000000..392cb19050 --- /dev/null +++ b/spring-cloud-modules/spring-cloud-openfeign/src/test/java/com/baeldung/cloud/openfeign/defaulterrorhandling/client/ProductClientUnitTest.java @@ -0,0 +1,84 @@ +package com.baeldung.cloud.openfeign.defaulterrorhandling.client; + +import com.baeldung.cloud.openfeign.defaulterrorhandling.model.Product; +import com.github.tomakehurst.wiremock.WireMockServer; +import feign.FeignException; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.HttpStatus; +import org.springframework.test.context.junit4.SpringRunner; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ProductClientUnitTest { + + @Autowired + private ProductClient productClient; + + private WireMockServer wireMockServer; + + @Before + public void startWireMockServer() { + wireMockServer = new WireMockServer(8081); + configureFor("localhost", 8081); + wireMockServer.start(); + } + + @After + public void stopWireMockServer() { + wireMockServer.stop(); + } + + @Test + public void givenProductIsAvailable_whenGetProductCalled_thenReturnMatchingProduct() { + String productId = "test"; + + String productResponse = "{ " + + " \"id\":\"test\",\n" + + " \"productName\":\"Watermelon\",\n" + + " \"price\":12\n" + + "}"; + + stubFor(get(urlEqualTo("/product/" + productId)) + .willReturn(aResponse() + .withStatus(HttpStatus.OK.value()) + .withHeader("Content-Type", "application/json") + .withBody(productResponse))); + + Product product = productClient.getProduct(productId); + + assertEquals(productId, product.getId()); + assertEquals("Watermelon", product.getProductName()); + assertEquals(12.00d, product.getPrice(), 0.00d); + } + + @Test + public void givenProductApiIsNotAvailable_whenGetProductCalled_thenThrowFeignException() { + String productId = "test"; + + stubFor(get(urlEqualTo("/product/" + productId)) + .willReturn(aResponse() + .withStatus(HttpStatus.SERVICE_UNAVAILABLE.value()))); + + assertThrows(FeignException.class, () -> productClient.getProduct(productId)); + } + + @Test + public void givenProductIdNotFound_whenGetProductCalled_thenThrowFeignException() { + String productId = "test"; + + stubFor(get(urlEqualTo("/product/" + productId)) + .willReturn(aResponse() + .withStatus(HttpStatus.NOT_FOUND.value()))); + + assertThrows(FeignException.class, () -> productClient.getProduct(productId)); + } +} diff --git a/spring-cloud-modules/spring-cloud-openfeign/src/test/java/com/baeldung/cloud/openfeign/defaulterrorhandling/controller/ProductControllerUnitTest.java b/spring-cloud-modules/spring-cloud-openfeign/src/test/java/com/baeldung/cloud/openfeign/defaulterrorhandling/controller/ProductControllerUnitTest.java new file mode 100644 index 0000000000..269cd2d2d6 --- /dev/null +++ b/spring-cloud-modules/spring-cloud-openfeign/src/test/java/com/baeldung/cloud/openfeign/defaulterrorhandling/controller/ProductControllerUnitTest.java @@ -0,0 +1,72 @@ +package com.baeldung.cloud.openfeign.defaulterrorhandling.controller; + +import com.baeldung.cloud.openfeign.defaulterrorhandling.client.ProductClient; +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.cloud.openfeign.FeignAutoConfiguration; +import org.springframework.http.HttpStatus; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + + +@RunWith(SpringRunner.class) +@WebMvcTest(ProductController.class) +@ImportAutoConfiguration({FeignAutoConfiguration.class, TestControllerAdvice.class}) +@EnableWebMvc +public class ProductControllerUnitTest { + + @Autowired + private ProductClient productClient; + + @Autowired + private MockMvc mockMvc; + + private WireMockServer wireMockServer; + + @Before + public void startWireMockServer() { + wireMockServer = new WireMockServer(8081); + configureFor("localhost", 8081); + wireMockServer.start(); + } + + @After + public void stopWireMockServer() { + wireMockServer.stop(); + } + + @Test + public void givenProductServiceIsnotAvailable_whenGetProductCalled_thenReturnInternalServerError() throws Exception { + String productId = "test"; + + stubFor(WireMock.get(urlEqualTo("/product/" + productId)) + .willReturn(aResponse().withStatus(HttpStatus.SERVICE_UNAVAILABLE.value()))); + + mockMvc.perform(get("/myapp1/product/" + productId)) + .andExpect(status().is(HttpStatus.INTERNAL_SERVER_ERROR.value())); + } + + + @Test + public void givenProductIsNotFound_whenGetProductCalled_thenReturnBadeRequestError() throws Exception { + String productId = "test"; + + stubFor(WireMock.get(urlEqualTo("/product/" + productId)) + .willReturn(aResponse().withStatus(HttpStatus.NOT_FOUND.value()))); + + mockMvc.perform(get("/myapp1/product/" +productId)) + .andExpect(status().is(HttpStatus.INTERNAL_SERVER_ERROR.value())); + } +} diff --git a/spring-cloud-modules/spring-cloud-openfeign/src/test/java/com/baeldung/cloud/openfeign/defaulterrorhandling/controller/TestControllerAdvice.java b/spring-cloud-modules/spring-cloud-openfeign/src/test/java/com/baeldung/cloud/openfeign/defaulterrorhandling/controller/TestControllerAdvice.java new file mode 100644 index 0000000000..0c7ed86b7c --- /dev/null +++ b/spring-cloud-modules/spring-cloud-openfeign/src/test/java/com/baeldung/cloud/openfeign/defaulterrorhandling/controller/TestControllerAdvice.java @@ -0,0 +1,16 @@ +package com.baeldung.cloud.openfeign.defaulterrorhandling.controller; + +import feign.FeignException; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice +public class TestControllerAdvice { + + @ExceptionHandler({FeignException.class}) + public ResponseEntity handleFeignException(FeignException exception) { + return new ResponseEntity<>(exception.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR); + } +}