11/*
2- * Copyright 2012-2020 the original author or authors.
2+ * Copyright 2012-2025 the original author or authors.
33 *
44 * Licensed under the Apache License, Version 2.0 (the "License");
55 * you may not use this file except in compliance with the License.
2727import org .junit .jupiter .api .BeforeEach ;
2828import org .junit .jupiter .api .Disabled ;
2929import org .junit .jupiter .api .Test ;
30+ import reactor .core .publisher .Mono ;
3031
3132import org .springframework .beans .factory .annotation .Autowired ;
32- import org .springframework .boot .SpringBootConfiguration ;
33- import org .springframework .boot .autoconfigure .EnableAutoConfiguration ;
34- import org .springframework .boot .test .context .SpringBootTest ;
3533import org .springframework .boot .test .web .server .LocalServerPort ;
3634import org .springframework .cloud .client .DefaultServiceInstance ;
3735import org .springframework .cloud .client .ServiceInstance ;
3836import org .springframework .cloud .client .discovery .DiscoveryClient ;
39- import org .springframework .cloud .client .discovery .EnableDiscoveryClient ;
4037import org .springframework .cloud .client .discovery .simple .SimpleDiscoveryProperties ;
4138import org .springframework .cloud .client .loadbalancer .CompletionContext ;
4239import org .springframework .cloud .client .loadbalancer .DefaultRequestContext ;
4542import org .springframework .cloud .client .loadbalancer .Request ;
4643import org .springframework .cloud .client .loadbalancer .Response ;
4744import org .springframework .cloud .client .loadbalancer .ResponseData ;
48- import org .springframework .context . annotation . Bean ;
45+ import org .springframework .http . HttpMethod ;
4946import org .springframework .http .HttpStatus ;
50- import org .springframework .web .bind .annotation .GetMapping ;
51- import org .springframework .web .bind .annotation .RestController ;
52- import org .springframework .web .reactive .function .client .ClientResponse ;
47+ import org .springframework .http .ResponseEntity ;
5348import org .springframework .web .reactive .function .client .WebClient ;
5449
5550import static org .assertj .core .api .Assertions .assertThat ;
56- import static org .assertj .core .api .Assertions .assertThatCode ;
51+ import static org .assertj .core .api .Assertions .assertThatIllegalStateException ;
52+ import static org .assertj .core .api .AssertionsForClassTypes .assertThatCode ;
5753import static org .assertj .core .api .BDDAssertions .then ;
58- import static org .springframework .boot .test .context .SpringBootTest .WebEnvironment .RANDOM_PORT ;
5954
6055/**
61- * Tests for {@link ReactorLoadBalancerExchangeFilterFunction} .
56+ * Base class for {@link LoadBalancedExchangeFilterFunction} integration tests .
6257 *
6358 * @author Olga Maciaszek-Sharma
64- * @author Charu Covindane
6559 */
66- @ SuppressWarnings ("ConstantConditions" )
67- @ SpringBootTest (webEnvironment = RANDOM_PORT )
68- class ReactorLoadBalancerExchangeFilterFunctionTests {
60+ @ SuppressWarnings ("DataFlowIssue" )
61+ abstract class AbstractLoadBalancerExchangeFilterFunctionIntegrationTests {
6962
7063 @ Autowired
71- private ReactorLoadBalancerExchangeFilterFunction loadBalancerFunction ;
64+ protected LoadBalancedExchangeFilterFunction loadBalancerFunction ;
7265
7366 @ Autowired
74- private SimpleDiscoveryProperties properties ;
67+ protected SimpleDiscoveryProperties properties ;
7568
7669 @ Autowired
77- private LoadBalancerProperties loadBalancerProperties ;
70+ protected LoadBalancerProperties loadBalancerProperties ;
7871
7972 @ Autowired
80- private ReactiveLoadBalancer .Factory <ServiceInstance > factory ;
73+ protected ReactiveLoadBalancer .Factory <ServiceInstance > factory ;
8174
8275 @ LocalServerPort
83- private int port ;
76+ protected int port ;
8477
8578 @ BeforeEach
86- void setUp () {
79+ protected void setUp () {
8780 DefaultServiceInstance instance = new DefaultServiceInstance ();
8881 instance .setServiceId ("testservice" );
89- instance .setUri (URI .create ("http://localhost:" + this . port ));
82+ instance .setUri (URI .create ("http://localhost:" + port ));
9083 DefaultServiceInstance instanceWithNoLifecycleProcessors = new DefaultServiceInstance ();
9184 instanceWithNoLifecycleProcessors .setServiceId ("serviceWithNoLifecycleProcessors" );
92- instanceWithNoLifecycleProcessors .setUri (URI .create ("http://localhost:" + this . port ));
85+ instanceWithNoLifecycleProcessors .setUri (URI .create ("http://localhost:" + port ));
9386 properties .getInstances ().put ("testservice" , Collections .singletonList (instance ));
9487 properties .getInstances ()
9588 .put ("serviceWithNoLifecycleProcessors" , Collections .singletonList (instanceWithNoLifecycleProcessors ));
9689 }
9790
9891 @ Test
9992 void correctResponseReturnedForExistingHostAndInstancePresent () {
100- ClientResponse clientResponse = WebClient .builder ()
93+ ResponseEntity < String > response = WebClient .builder ()
10194 .baseUrl ("http://testservice" )
10295 .filter (loadBalancerFunction )
10396 .build ()
10497 .get ()
10598 .uri ("/hello" )
106- .exchange ()
99+ .retrieve ()
100+ .toEntity (String .class )
107101 .block ();
108- then (clientResponse . statusCode ()).isEqualTo (HttpStatus .OK );
109- then (clientResponse . bodyToMono ( String . class ). block ()).isEqualTo ("Hello World" );
102+ then (response . getStatusCode ()).isEqualTo (HttpStatus .OK );
103+ then (response . getBody ()).isEqualTo ("Hello World" );
110104 }
111105
112106 @ Test
113107 void serviceUnavailableReturnedWhenNoInstancePresent () {
114- ClientResponse clientResponse = WebClient .builder ()
115- .baseUrl ("http://xxx" )
116- .filter (this .loadBalancerFunction )
117- .build ()
118- .get ()
119- .exchange ()
120- .block ();
121- then (clientResponse .statusCode ()).isEqualTo (HttpStatus .SERVICE_UNAVAILABLE );
108+ assertThatIllegalStateException ()
109+ .isThrownBy (() -> WebClient .builder ()
110+ .baseUrl ("http://xxx" )
111+ .filter (loadBalancerFunction )
112+ .defaultStatusHandler (httpStatusCode -> httpStatusCode .equals (HttpStatus .SERVICE_UNAVAILABLE ),
113+ clientResponse -> Mono .just (new IllegalStateException ("503" )))
114+ .build ()
115+ .get ()
116+ .retrieve ()
117+ .toBodilessEntity ()
118+ .block ())
119+ .withMessage ("503" );
122120 }
123121
124122 @ Test
125123 @ Disabled // FIXME 3.0.0
126124 void badRequestReturnedForIncorrectHost () {
127- ClientResponse clientResponse = WebClient .builder ()
128- .baseUrl ("http:///xxx" )
129- .filter (this .loadBalancerFunction )
130- .build ()
131- .get ()
132- .exchange ()
133- .block ();
134- then (clientResponse .statusCode ()).isEqualTo (HttpStatus .BAD_REQUEST );
125+ assertThatIllegalStateException ()
126+ .isThrownBy (() -> WebClient .builder ()
127+ .baseUrl ("http:///xxx" )
128+ .filter (loadBalancerFunction )
129+ .defaultStatusHandler (httpStatusCode -> httpStatusCode .equals (HttpStatus .BAD_REQUEST ),
130+ response -> Mono .just (new IllegalStateException ("400" )))
131+ .build ()
132+ .get ()
133+ .retrieve ()
134+ .toBodilessEntity ()
135+ .block ())
136+ .withMessage ("400" );
135137 }
136138
137139 @ Test
@@ -142,97 +144,88 @@ void exceptionNotThrownWhenFactoryReturnsNullLifecycleProcessorsMap() {
142144 .build ()
143145 .get ()
144146 .uri ("/hello" )
145- .exchange ( )
147+ .exchangeToMono ( clientResponse -> clientResponse . bodyToMono ( String . class ) )
146148 .block ()).doesNotThrowAnyException ();
147149 }
148150
149151 @ Test
150152 void loadBalancerLifecycleCallbacksExecuted () {
151153 final String callbackTestHint = "callbackTestHint" ;
152154 loadBalancerProperties .getHint ().put ("testservice" , "callbackTestHint" );
153- ClientResponse clientResponse = WebClient .builder ()
155+
156+ ResponseEntity <Void > response = WebClient .builder ()
154157 .baseUrl ("http://testservice" )
155158 .filter (loadBalancerFunction )
156159 .build ()
157160 .get ()
158161 .uri ("/callback" )
159- .exchange ()
162+ .retrieve ()
163+ .toBodilessEntity ()
160164 .block ();
161165
162166 Collection <Request <Object >> lifecycleLogRequests = ((TestLoadBalancerLifecycle ) factory
163167 .getInstances ("testservice" , LoadBalancerLifecycle .class )
164168 .get ("loadBalancerLifecycle" )).getStartLog ().values ();
165- Collection <Request <Object >> lifecycleStartedLogRequests = ((TestLoadBalancerLifecycle ) factory
169+ Collection <Request <Object >> lifecycleLogStartRequests = ((TestLoadBalancerLifecycle ) factory
166170 .getInstances ("testservice" , LoadBalancerLifecycle .class )
167171 .get ("loadBalancerLifecycle" )).getStartRequestLog ().values ();
168172 Collection <CompletionContext <Object , ServiceInstance , Object >> anotherLifecycleLogRequests = ((AnotherLoadBalancerLifecycle ) factory
169173 .getInstances ("testservice" , LoadBalancerLifecycle .class )
170174 .get ("anotherLoadBalancerLifecycle" )).getCompleteLog ().values ();
171- then (clientResponse . statusCode ()).isEqualTo (HttpStatus .OK );
175+ then (response . getStatusCode ()).isEqualTo (HttpStatus .OK );
172176 assertThat (lifecycleLogRequests ).extracting (request -> ((DefaultRequestContext ) request .getContext ()).getHint ())
173177 .contains (callbackTestHint );
174- assertThat (lifecycleStartedLogRequests )
178+ assertThat (lifecycleLogStartRequests )
175179 .extracting (request -> ((DefaultRequestContext ) request .getContext ()).getHint ())
176180 .contains (callbackTestHint );
177181 assertThat (anotherLifecycleLogRequests )
178182 .extracting (completionContext -> ((ResponseData ) completionContext .getClientResponse ()).getRequestData ()
179- .getUrl ()
180- .toString ())
181- .contains ("http://testservice/callback" );
183+ .getHttpMethod ())
184+ .contains (HttpMethod .GET );
182185 }
183186
184- @ SuppressWarnings ({ "unchecked" , "rawtypes" })
185- @ EnableDiscoveryClient
186- @ EnableAutoConfiguration
187- @ SpringBootConfiguration (proxyBeanMethods = false )
188- @ RestController
189- static class Config {
187+ protected static class TestLoadBalancerFactory implements ReactiveLoadBalancer .Factory <ServiceInstance > {
188+
189+ private final ReactorLoadBalancerExchangeFilterFunctionIntegrationTests .TestLoadBalancerLifecycle testLoadBalancerLifecycle ;
190+
191+ private final ReactorLoadBalancerExchangeFilterFunctionIntegrationTests .TestLoadBalancerLifecycle anotherLoadBalancerLifecycle ;
190192
191- @ GetMapping ("/hello" )
192- public String hello () {
193- return "Hello World" ;
193+ private final DiscoveryClient discoveryClient ;
194+
195+ private final LoadBalancerProperties properties ;
196+
197+ public TestLoadBalancerFactory (DiscoveryClient discoveryClient , LoadBalancerProperties properties ) {
198+ this .discoveryClient = discoveryClient ;
199+ this .properties = properties ;
200+ testLoadBalancerLifecycle = new ReactorLoadBalancerExchangeFilterFunctionIntegrationTests .TestLoadBalancerLifecycle ();
201+ anotherLoadBalancerLifecycle = new ReactorLoadBalancerExchangeFilterFunctionIntegrationTests .AnotherLoadBalancerLifecycle ();
194202 }
195203
196- @ GetMapping ( "/callback" )
197- String callbackTestResult ( ) {
198- return "callbackTestResult" ;
204+ @ Override
205+ public ReactiveLoadBalancer < ServiceInstance > getInstance ( String serviceId ) {
206+ return new DiscoveryClientBasedReactiveLoadBalancer ( serviceId , discoveryClient ) ;
199207 }
200208
201- @ Bean
202- ReactiveLoadBalancer .Factory <ServiceInstance > reactiveLoadBalancerFactory (DiscoveryClient discoveryClient ,
203- LoadBalancerProperties properties ) {
204- return new ReactiveLoadBalancer .Factory <>() {
205-
206- private final TestLoadBalancerLifecycle testLoadBalancerLifecycle = new TestLoadBalancerLifecycle ();
207-
208- private final TestLoadBalancerLifecycle anotherLoadBalancerLifecycle = new AnotherLoadBalancerLifecycle ();
209-
210- @ Override
211- public ReactiveLoadBalancer <ServiceInstance > getInstance (String serviceId ) {
212- return new DiscoveryClientBasedReactiveLoadBalancer (serviceId , discoveryClient );
213- }
214-
215- @ Override
216- public <X > Map <String , X > getInstances (String name , Class <X > type ) {
217- if (name .equals ("serviceWithNoLifecycleProcessors" )) {
218- return null ;
219- }
220- Map lifecycleProcessors = new HashMap <>();
221- lifecycleProcessors .put ("loadBalancerLifecycle" , testLoadBalancerLifecycle );
222- lifecycleProcessors .put ("anotherLoadBalancerLifecycle" , anotherLoadBalancerLifecycle );
223- return lifecycleProcessors ;
224- }
225-
226- @ Override
227- public <X > X getInstance (String name , Class <?> clazz , Class <?>... generics ) {
228- return null ;
229- }
230-
231- @ Override
232- public LoadBalancerProperties getProperties (String serviceId ) {
233- return properties ;
234- }
235- };
209+ @ SuppressWarnings ({ "rawtypes" , "unchecked" })
210+ @ Override
211+ public <X > Map <String , X > getInstances (String name , Class <X > type ) {
212+ if (name .equals ("serviceWithNoLifecycleProcessors" )) {
213+ return null ;
214+ }
215+ Map lifecycleProcessors = new HashMap <>();
216+ lifecycleProcessors .put ("loadBalancerLifecycle" , testLoadBalancerLifecycle );
217+ lifecycleProcessors .put ("anotherLoadBalancerLifecycle" , anotherLoadBalancerLifecycle );
218+ return lifecycleProcessors ;
219+ }
220+
221+ @ Override
222+ public <X > X getInstance (String name , Class <?> clazz , Class <?>... generics ) {
223+ return null ;
224+ }
225+
226+ @ Override
227+ public LoadBalancerProperties getProperties (String serviceId ) {
228+ return properties ;
236229 }
237230
238231 }
@@ -257,6 +250,7 @@ public void onStartRequest(Request<Object> request, Response<ServiceInstance> lb
257250
258251 @ Override
259252 public void onComplete (CompletionContext <Object , ServiceInstance , Object > completionContext ) {
253+ completeLog .clear ();
260254 completeLog .put (getName () + UUID .randomUUID (), completionContext );
261255 }
262256
@@ -273,18 +267,13 @@ Map<String, Request<Object>> getStartRequestLog() {
273267 }
274268
275269 protected String getName () {
276- return this . getClass ().getSimpleName ();
270+ return getClass ().getSimpleName ();
277271 }
278272
279273 }
280274
281275 protected static class AnotherLoadBalancerLifecycle extends TestLoadBalancerLifecycle {
282276
283- @ Override
284- protected String getName () {
285- return this .getClass ().getSimpleName ();
286- }
287-
288277 }
289278
290279}
0 commit comments