Context
When using RestClientTest, it's currently not possible to inject a qualified name for the bean.
For instance, we may need to configure the productServiceRestTemplate differently to others, i.e. for adding required authentication headers, and inject it as so:
public ProductServiceClient(@Qualifier("productServiceRestTemplate") RestTemplate template) {
// ...
}
Unfortunately this does not work, leading to the following error:
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'org.springframework.web.client.RestTemplate' available: expected single matching bean but found 2: productServiceRestTemplate,defaultRestTemplate
It'd be ideal if we could specify the name for the bean to be injected, to allow component-scanning to work.
Alternatives
We can currently do this using @TestConfiguration and removing the @RestClientTest's autoconfiguration of the i.e. ProductServiceClient, so it's not impossible to do, it just may be useful to allow this to be easier?
Comment From: snicoll
Thanks for the suggestion, @RestClientTest is primarily intended to work with RestTemplateBuilder. Would it be possible to refactor ProductServiceClient to take a builder and customize it to create the template internally?
Comment From: spring-projects-issues
If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.
Comment From: jamietanna
The idea for providing a pre-configured RestTemplate is so we don't need to have the constructor doing a load of logic, making it harder to ie unit test
Comment From: wilkinsona
@jamietanna We generally do not recommend defining a RestTemplate as a bean as we find that there are often multiple users of RestTemplate in an application, each with different needs in terms of how the template is configured. For this reason, we encourage customizing the RestTemplateBuilder with common configuration instead. The builder is then injected and used to build a specialised RestTemplate that meets each injection point's needs. Note that the builder is a prototype bean so each consumer can configure it without affecting the other consumers.
As far as I recall and can find, we don't try to consume a RestTemplate bean from any of the code that backs @RestClientTest so it's not clear to me what's causing the NoUniqueBeanDefinitionException or how allowing a qualifier to be specified on @RestClientTest would help. To help us to understand the problem that you're facing, can you please share a minimal sample that illustrates your RestTemplate arrangement and that includes a test that reproduces the NoUniqueBeanDefinitionException? You can share it with us by zipping it up and attaching it to this issue or by pushing it to a separate repository on GitHub.
Comment From: spring-projects-issues
If you would like us to look at this issue, please provide the requested information. If the information is not provided within the next 7 days this issue will be closed.
Comment From: spring-projects-issues
Closing due to lack of requested feedback. If you would like us to look at this issue, please provide the requested information and we will re-open the issue.
Comment From: cranberrysoft
@jamietanna, my solution for the problem you described
@Autowired
SomeClient sut; //the class you test
MockRestServiceServer mockServer;
@Autowired
MockServerRestTemplateCustomizer customizer;
@Autowired
@Qualifier("productServiceRestTemplate")
RestTemplate productServiceRestTemplate;
@BeforeEach
public void setUp() {
//We manually set the mock server as there might be more than one JdbcTemplate configured.
mockServer = customizer.getServer(productServiceRestTemplate);
}
@AfterEach
public void tearDown() {
mockServer.reset();
}
By default, MockServerRestTemplateCustomizer is configured for your RestTemplateBuilder, and hence every RestTemplate gets it as well. The autoconfiguration for injected bean MockRestServiceServer, unfortunately, is not enough "smart" to understand which JdbcTemplate you want to exercise.
Regarding comment @wilkinsona I don't 100% agree that you are supposed to pass RestTemplateBuilder to a service class and use it within a constructor of your class to create an instance of RestTemplate. The reason is, as @jamietanna mentioned, it leads to bloated constructors. I believe good constructors should be simple, and any configuration should be done, for example, with factories. In my opinion, the best practice is to create a @Configuration class that configures RestTemplate @Bean annotated with @Qualifier dedicated for your client. This way, we can decouple the configuration for the RestClient and the client class implementation.
For example, when I was working working with RestTemplateBuilder my constructor ended up as below.
public SomeClient(RestTemplateBuilder restTemplateBuilder,
ServiceUrlConfig serviceUrlConfig,
ServiceTimeoutConfig serviceTimeoutConfig) {
final MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter =
new MappingJackson2HttpMessageConverter(Jackson2ObjectMapperBuilder.json()
.featuresToDisable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE)
.build());
mappingJackson2HttpMessageConverter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON));
this.restTemplate = restTemplateBuilder
.messageConverters(mappingJackson2HttpMessageConverter)
.rootUri(serviceUrlConfig.get(SERVICE_NAME).toString())
.setConnectTimeout(serviceTimeoutConfig.getTimeout(SERVICE_NAME).getConnect())
.setReadTimeout(serviceTimeoutConfig.getTimeout(SERVICE_NAME).getRead()).build();
}
Generally speaking, I think the current implementation of @RestClientTest is not flexible enough. I would expect there should be an option to configure a Filter on JdbcTemplate like @RestClientTest(restTemplateFilter=<definition>)