ReactorLoadBalancerClientAutoConfigurationTests.java
/*
* Copyright 2012-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.client.loadbalancer.reactive;
import java.time.Duration;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.webclient.autoconfigure.service.ReactiveHttpClientServiceProperties;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.client.loadbalancer.LoadBalancedRetryFactory;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClientsProperties;
import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties;
import org.springframework.cloud.client.loadbalancer.LoadBalancerRestClientHttpServiceGroupConfigurer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.web.reactive.function.client.WebClient;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.BDDAssertions.then;
import static org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerTestUtils.assertLoadBalanced;
import static org.springframework.cloud.client.loadbalancer.reactive.LoadBalancerTestUtils.getFilters;
/**
* Tests for {@link ReactorLoadBalancerClientAutoConfiguration}.
*
* @author Olga Maciaszek-Sharma
*/
public class ReactorLoadBalancerClientAutoConfigurationTests {
@Test
void loadBalancerFilterAddedToWebClientBuilder() {
ConfigurableApplicationContext context = init(OneWebClientBuilder.class);
final Map<String, WebClient.Builder> webClientBuilders = context.getBeansOfType(WebClient.Builder.class);
then(webClientBuilders).isNotNull().hasSize(1);
WebClient.Builder webClientBuilder = webClientBuilders.values().iterator().next();
then(webClientBuilder).isNotNull();
assertLoadBalanced(webClientBuilder, ReactorLoadBalancerExchangeFilterFunction.class);
final Map<String, OneWebClientBuilder.TestService> testServiceMap = context
.getBeansOfType(OneWebClientBuilder.TestService.class);
then(testServiceMap).isNotNull().hasSize(1);
OneWebClientBuilder.TestService testService = testServiceMap.values().stream().findFirst().get();
assertLoadBalanced(testService.webClient, ReactorLoadBalancerExchangeFilterFunction.class);
}
@Test
void loadBalancerFilterAddedToWebClientBuilderWithRetryEnabled() {
System.setProperty("spring.cloud.loadbalancer.retry.enabled", "true");
ConfigurableApplicationContext context = init(OneWebClientBuilder.class);
final Map<String, WebClient.Builder> webClientBuilders = context.getBeansOfType(WebClient.Builder.class);
then(webClientBuilders).isNotNull().hasSize(1);
WebClient.Builder webClientBuilder = webClientBuilders.values().iterator().next();
then(webClientBuilder).isNotNull();
assertLoadBalanced(webClientBuilder, RetryableLoadBalancerExchangeFilterFunction.class);
final Map<String, OneWebClientBuilder.TestService> testServiceMap = context
.getBeansOfType(OneWebClientBuilder.TestService.class);
then(testServiceMap).isNotNull().hasSize(1);
OneWebClientBuilder.TestService testService = testServiceMap.values().stream().findFirst().get();
assertLoadBalanced(testService.webClient, RetryableLoadBalancerExchangeFilterFunction.class);
System.clearProperty("spring.cloud.loadbalancer.retry.enabled");
}
@Test
void loadBalancerFilterAddedOnlyToLoadBalancedWebClientBuilder() {
ConfigurableApplicationContext context = init(TwoWebClientBuilders.class);
final Map<String, WebClient.Builder> webClientBuilders = context.getBeansOfType(WebClient.Builder.class);
then(webClientBuilders).hasSize(2);
TwoWebClientBuilders.Two two = context.getBean(TwoWebClientBuilders.Two.class);
then(two.loadBalanced).isNotNull();
assertLoadBalanced(two.loadBalanced, ReactorLoadBalancerExchangeFilterFunction.class);
then(two.nonLoadBalanced).isNotNull();
then(getFilters(two.nonLoadBalanced)).isNullOrEmpty();
}
@Test
void loadBalancerFilterAddedOnlyToLoadBalancedWebClientBuilderWithRetryEnabled() {
System.setProperty("spring.cloud.loadbalancer.retry.enabled", "true");
ConfigurableApplicationContext context = init(TwoWebClientBuilders.class);
final Map<String, WebClient.Builder> webClientBuilders = context.getBeansOfType(WebClient.Builder.class);
then(webClientBuilders).hasSize(2);
TwoWebClientBuilders.Two two = context.getBean(TwoWebClientBuilders.Two.class);
then(two.loadBalanced).isNotNull();
assertLoadBalanced(two.loadBalanced, RetryableLoadBalancerExchangeFilterFunction.class);
then(two.nonLoadBalanced).isNotNull();
then(getFilters(two.nonLoadBalanced)).isNullOrEmpty();
System.clearProperty("spring.cloud.loadbalancer.retry.enabled");
}
@Test
void noCustomWebClientBuilders() {
ConfigurableApplicationContext context = init(NoWebClientBuilder.class);
final Map<String, WebClient.Builder> webClientBuilders = context.getBeansOfType(WebClient.Builder.class);
then(webClientBuilders).hasSize(1);
WebClient.Builder builder = context.getBean(WebClient.Builder.class);
then(builder).isNotNull();
then(getFilters(builder)).isNullOrEmpty();
}
@Test
void defaultPropertiesWorks() {
ConfigurableApplicationContext context = new SpringApplicationBuilder().web(WebApplicationType.NONE)
.sources(OneWebClientBuilder.class, DefaulConfig.class)
.properties("spring.cloud.loadbalancer.health-check.initial-delay=1s",
"spring.cloud.loadbalancer.clients.myclient.health-check.interval=30s")
.run();
LoadBalancerClientsProperties properties = context.getBean(LoadBalancerClientsProperties.class);
then(properties.getClients()).containsKey("myclient");
LoadBalancerProperties clientProperties = properties.getClients().get("myclient");
// default value
then(clientProperties.getHealthCheck().getInitialDelay()).isEqualTo(Duration.ofSeconds(1));
// client specific value
then(clientProperties.getHealthCheck().getInterval()).isEqualTo(Duration.ofSeconds(30));
}
@Test
void loadBalancerRestClientHttpServiceGroupConfigurerPresent() {
new ApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(ReactorLoadBalancerClientAutoConfiguration.class,
LoadBalancerBeanPostProcessorAutoConfiguration.class))
.withUserConfiguration(OneWebClientBuilder.class)
.run(context -> {
assertThat(context.getBeansOfType(LoadBalancerWebClientHttpServiceGroupConfigurer.class)).hasSize(1);
assertThat(context.getBeansOfType(LoadBalancerRestClientHttpServiceGroupConfigurer.class)).hasSize(0);
});
}
private ConfigurableApplicationContext init(Class<?> config) {
return LoadBalancerTestUtils.init(config, ReactorLoadBalancerClientAutoConfiguration.class,
LoadBalancerBeanPostProcessorAutoConfiguration.class);
}
@Configuration
@EnableAutoConfiguration
protected static class DefaulConfig {
}
@Configuration
protected static class NoWebClientBuilder {
@Bean
ReactiveLoadBalancer.Factory<ServiceInstance> reactiveLoadBalancerFactory(LoadBalancerProperties properties) {
return new ReactiveLoadBalancer.Factory<>() {
@Override
public ReactiveLoadBalancer<ServiceInstance> getInstance(String serviceId) {
return new TestReactiveLoadBalancer();
}
@Override
public <X> Map<String, X> getInstances(String name, Class<X> type) {
throw new UnsupportedOperationException("Not implemented");
}
@Override
public <X> X getInstance(String name, Class<?> clazz, Class<?>... generics) {
throw new UnsupportedOperationException("Not implemented.");
}
@Override
public LoadBalancerProperties getProperties(String serviceId) {
return properties;
}
};
}
@Bean
LoadBalancedRetryFactory loadBalancedRetryFactory() {
return new LoadBalancedRetryFactory() {
};
}
}
@Configuration
protected static class OneWebClientBuilder extends NoWebClientBuilder {
@Bean
@LoadBalanced
WebClient.Builder loadBalancedWebClientBuilder() {
return WebClient.builder();
}
@Bean
TestService testService() {
return new TestService(loadBalancedWebClientBuilder());
}
@Bean
@Primary
ReactiveHttpClientServiceProperties reactiveHttpClientServiceProperties() {
return new ReactiveHttpClientServiceProperties();
}
private static final class TestService {
public final WebClient webClient;
private TestService(WebClient.Builder builder) {
this.webClient = builder.build();
}
}
}
@Configuration
protected static class TwoWebClientBuilders extends OneWebClientBuilder {
@Primary
@Bean
WebClient.Builder webClientBuilder() {
return WebClient.builder();
}
@Configuration
protected static class Two {
@Autowired
WebClient.Builder nonLoadBalanced;
@Autowired
@LoadBalanced
WebClient.Builder loadBalanced;
}
}
}