From e15bd271fb27037ddcad99274c8b578286d85dc6 Mon Sep 17 00:00:00 2001 From: bala Date: Thu, 25 Jun 2026 19:26:27 +0100 Subject: [PATCH 1/2] BAEL-8406: added implementation for testing a spring mvc handler interceptor --- .../DeliveryChargeInterceptor.java | 37 ++++++++++ .../DeliveryChargeService.java | 9 +++ .../DeliveryChargesController.java | 26 +++++++ .../FeatureFlagService.java | 5 ++ .../HandlerInterceptorApp.java | 11 +++ .../handlerinterceptor/WebMvcConfig.java | 21 ++++++ ...iveryChargeInterceptorIntegrationTest.java | 72 +++++++++++++++++++ 7 files changed, 181 insertions(+) create mode 100644 spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/handlerinterceptor/DeliveryChargeInterceptor.java create mode 100644 spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/handlerinterceptor/DeliveryChargeService.java create mode 100644 spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/handlerinterceptor/DeliveryChargesController.java create mode 100644 spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/handlerinterceptor/FeatureFlagService.java create mode 100644 spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/handlerinterceptor/HandlerInterceptorApp.java create mode 100644 spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/handlerinterceptor/WebMvcConfig.java create mode 100644 spring-boot-modules/spring-boot-mvc-5/src/test/java/com/baeldung/handlerinterceptor/DeliveryChargeInterceptorIntegrationTest.java diff --git a/spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/handlerinterceptor/DeliveryChargeInterceptor.java b/spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/handlerinterceptor/DeliveryChargeInterceptor.java new file mode 100644 index 000000000000..7978ac2aa941 --- /dev/null +++ b/spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/handlerinterceptor/DeliveryChargeInterceptor.java @@ -0,0 +1,37 @@ +package com.baeldung.handlerinterceptor; + +import java.security.SecureRandom; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerInterceptor; + +import org.springframework.lang.NonNull; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +public class DeliveryChargeInterceptor implements HandlerInterceptor { + + static final String USE_V2_ATTRIBUTE = "useV2"; + + private final FeatureFlagService featureFlagService; + private final SecureRandom random = new SecureRandom(); + private final Logger logger = LoggerFactory.getLogger(DeliveryChargeInterceptor.class); + + public DeliveryChargeInterceptor(FeatureFlagService featureFlagService) { + this.featureFlagService = featureFlagService; + } + + @Override + public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) { + if (handler instanceof HandlerMethod) { + int rollout = featureFlagService.rolloutPercentage(); + boolean useV2 = rollout > 0 && random.nextInt(100) < rollout; + request.setAttribute(USE_V2_ATTRIBUTE, useV2); + logger.info("Delivery charge feature: rollout={}%, useV2={}", rollout, useV2); + } + return true; + } +} diff --git a/spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/handlerinterceptor/DeliveryChargeService.java b/spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/handlerinterceptor/DeliveryChargeService.java new file mode 100644 index 000000000000..04e8afea42ee --- /dev/null +++ b/spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/handlerinterceptor/DeliveryChargeService.java @@ -0,0 +1,9 @@ +package com.baeldung.handlerinterceptor; + +public interface DeliveryChargeService { + + double calculateV1(String postCode); + + double calculateV2(String postCode); + +} diff --git a/spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/handlerinterceptor/DeliveryChargesController.java b/spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/handlerinterceptor/DeliveryChargesController.java new file mode 100644 index 000000000000..437306534058 --- /dev/null +++ b/spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/handlerinterceptor/DeliveryChargesController.java @@ -0,0 +1,26 @@ +package com.baeldung.handlerinterceptor; + +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import jakarta.servlet.http.HttpServletRequest; + +@RestController +public class DeliveryChargesController { + + private final DeliveryChargeService deliveryChargeService; + + public DeliveryChargesController(DeliveryChargeService deliveryChargeService) { + this.deliveryChargeService = deliveryChargeService; + } + + @PostMapping("/delivery-charges/calculate") + public double calculate(@RequestParam String postcode, HttpServletRequest request) { + Boolean useV2 = (Boolean) request.getAttribute(DeliveryChargeInterceptor.USE_V2_ATTRIBUTE); + if (Boolean.TRUE.equals(useV2)) { + return deliveryChargeService.calculateV2(postcode); + } + return deliveryChargeService.calculateV1(postcode); + } +} diff --git a/spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/handlerinterceptor/FeatureFlagService.java b/spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/handlerinterceptor/FeatureFlagService.java new file mode 100644 index 000000000000..54656521aead --- /dev/null +++ b/spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/handlerinterceptor/FeatureFlagService.java @@ -0,0 +1,5 @@ +package com.baeldung.handlerinterceptor; + +public interface FeatureFlagService { + int rolloutPercentage(); +} diff --git a/spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/handlerinterceptor/HandlerInterceptorApp.java b/spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/handlerinterceptor/HandlerInterceptorApp.java new file mode 100644 index 000000000000..5d254e0c393d --- /dev/null +++ b/spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/handlerinterceptor/HandlerInterceptorApp.java @@ -0,0 +1,11 @@ +package com.baeldung.handlerinterceptor; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication(scanBasePackages = "com.baeldung.handlerinterceptor") +public class HandlerInterceptorApp { + public static void main(String[] args) { + SpringApplication.run(HandlerInterceptorApp.class, args); + } +} \ No newline at end of file diff --git a/spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/handlerinterceptor/WebMvcConfig.java b/spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/handlerinterceptor/WebMvcConfig.java new file mode 100644 index 000000000000..06709cda097b --- /dev/null +++ b/spring-boot-modules/spring-boot-mvc-5/src/main/java/com/baeldung/handlerinterceptor/WebMvcConfig.java @@ -0,0 +1,21 @@ +package com.baeldung.handlerinterceptor; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebMvcConfig implements WebMvcConfigurer { + + private final FeatureFlagService featureFlagService; + + public WebMvcConfig(FeatureFlagService featureFlagService) { + this.featureFlagService = featureFlagService; + } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(new DeliveryChargeInterceptor(featureFlagService)) + .addPathPatterns("/delivery-charges/**"); + } +} diff --git a/spring-boot-modules/spring-boot-mvc-5/src/test/java/com/baeldung/handlerinterceptor/DeliveryChargeInterceptorIntegrationTest.java b/spring-boot-modules/spring-boot-mvc-5/src/test/java/com/baeldung/handlerinterceptor/DeliveryChargeInterceptorIntegrationTest.java new file mode 100644 index 000000000000..dbb93d98ecf2 --- /dev/null +++ b/spring-boot-modules/spring-boot-mvc-5/src/test/java/com/baeldung/handlerinterceptor/DeliveryChargeInterceptorIntegrationTest.java @@ -0,0 +1,72 @@ +package com.baeldung.handlerinterceptor; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.web.servlet.MockMvc; + +@WebMvcTest(controllers = DeliveryChargesController.class) +class DeliveryChargeInterceptorIntegrationTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private DeliveryChargeService deliveryChargeService; + + @MockBean + private FeatureFlagService featureFlagService; + + @Test + void whenRolloutIsZero_thenV1IsUsed() throws Exception { + when(featureFlagService.rolloutPercentage()).thenReturn(0); + when(deliveryChargeService.calculateV1("SW1A")).thenReturn(5.0); + + mockMvc.perform(post("/delivery-charges/calculate").param("postcode", "SW1A")) + .andExpect(status().isOk()) + .andExpect(content().string("5.0")); + } + + @Test + void whenRolloutIs100_thenV2IsUsed() throws Exception { + when(featureFlagService.rolloutPercentage()).thenReturn(100); + when(deliveryChargeService.calculateV2("SW1A")).thenReturn(3.5); + + mockMvc.perform(post("/delivery-charges/calculate").param("postcode", "SW1A")) + .andExpect(status().isOk()) + .andExpect(content().string("3.5")); + } + + @Test + void whenRolloutIs50_thenBothVersionsAreUsed() throws Exception { + when(featureFlagService.rolloutPercentage()).thenReturn(50); + when(deliveryChargeService.calculateV1("SW1A")).thenReturn(5.0); + when(deliveryChargeService.calculateV2("SW1A")).thenReturn(3.5); + + int v1Count = 0; + int v2Count = 0; + + for (int i = 0; i < 20; i++) { + String response = mockMvc.perform(post("/delivery-charges/calculate").param("postcode", "SW1A")) + .andExpect(status().isOk()) + .andReturn() + .getResponse() + .getContentAsString(); + if ("5.0".equals(response)) { + v1Count++; + } else if ("3.5".equals(response)) { + v2Count++; + } + } + + assertThat(v1Count).isGreaterThan(0); + assertThat(v2Count).isGreaterThan(0); + } +} From a96a768e661f8ab23065b2e82242ab9252fa10b7 Mon Sep 17 00:00:00 2001 From: bala Date: Sun, 28 Jun 2026 08:32:35 +0100 Subject: [PATCH 2/2] BAEL-8406: renamed test class and corrected test names --- ...onTest.java => DeliveryChargeInterceptorUnitTest.java} | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) rename spring-boot-modules/spring-boot-mvc-5/src/test/java/com/baeldung/handlerinterceptor/{DeliveryChargeInterceptorIntegrationTest.java => DeliveryChargeInterceptorUnitTest.java} (88%) diff --git a/spring-boot-modules/spring-boot-mvc-5/src/test/java/com/baeldung/handlerinterceptor/DeliveryChargeInterceptorIntegrationTest.java b/spring-boot-modules/spring-boot-mvc-5/src/test/java/com/baeldung/handlerinterceptor/DeliveryChargeInterceptorUnitTest.java similarity index 88% rename from spring-boot-modules/spring-boot-mvc-5/src/test/java/com/baeldung/handlerinterceptor/DeliveryChargeInterceptorIntegrationTest.java rename to spring-boot-modules/spring-boot-mvc-5/src/test/java/com/baeldung/handlerinterceptor/DeliveryChargeInterceptorUnitTest.java index dbb93d98ecf2..f0f2fd69a48d 100644 --- a/spring-boot-modules/spring-boot-mvc-5/src/test/java/com/baeldung/handlerinterceptor/DeliveryChargeInterceptorIntegrationTest.java +++ b/spring-boot-modules/spring-boot-mvc-5/src/test/java/com/baeldung/handlerinterceptor/DeliveryChargeInterceptorUnitTest.java @@ -13,7 +13,7 @@ import org.springframework.test.web.servlet.MockMvc; @WebMvcTest(controllers = DeliveryChargesController.class) -class DeliveryChargeInterceptorIntegrationTest { +class DeliveryChargeInterceptorUnitTest { @Autowired private MockMvc mockMvc; @@ -25,7 +25,7 @@ class DeliveryChargeInterceptorIntegrationTest { private FeatureFlagService featureFlagService; @Test - void whenRolloutIsZero_thenV1IsUsed() throws Exception { + void givenZeroRollout_whenCalculateDeliveryCharge_thenV1IsUsed() throws Exception { when(featureFlagService.rolloutPercentage()).thenReturn(0); when(deliveryChargeService.calculateV1("SW1A")).thenReturn(5.0); @@ -35,7 +35,7 @@ void whenRolloutIsZero_thenV1IsUsed() throws Exception { } @Test - void whenRolloutIs100_thenV2IsUsed() throws Exception { + void givenFullRollout_whenCalculateDeliveryCharge_thenV2IsUsed() throws Exception { when(featureFlagService.rolloutPercentage()).thenReturn(100); when(deliveryChargeService.calculateV2("SW1A")).thenReturn(3.5); @@ -45,7 +45,7 @@ void whenRolloutIs100_thenV2IsUsed() throws Exception { } @Test - void whenRolloutIs50_thenBothVersionsAreUsed() throws Exception { + void givenPartialRollout_whenCalculateDeliveryCharge_thenBothVersionsAreUsed() throws Exception { when(featureFlagService.rolloutPercentage()).thenReturn(50); when(deliveryChargeService.calculateV1("SW1A")).thenReturn(5.0); when(deliveryChargeService.calculateV2("SW1A")).thenReturn(3.5);